SQL Server 2014 STUFF with 2 Inner Join - sql

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.

Related

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 |

Select Customers where not have value in other table with join

I need to select all Customers from the table Customer where Value in table Customer_Value is not 4.
Customers:
+------------+-------+
| Customer | ... |
+------------+-------+
| 312 | ... |
| 345 | ... |
| 678 | ... |
+------------+-------+
Customer_Value:
+------------+-------+
| Customer | Value |
+------------+-------+
| 312 | 1 |
| 312 | 2 |
| 345 | 1 |
| 345 | 2 |
| 345 | 3 |
| 678 | 1 |
| 678 | 2 |
| 678 | 4 |
+------------+-------+
To get my result I've used the following query:
SELECT C.Customer FROM [Customer] C
Left join Customer_Value V ON (C.Customer = V.Customer)
WHERE C.Customer NOT IN (SELECT Customer FROM [Customer_Value] WHERE Value = '4')
GROUP BY C.Customer
So my question is:
Is that a fast and good query? Or are there some other better solutions to get all the Customer Ids?
You can avoid Negative condition using Left Join and IS NULL Filter in where Condition.
SELECT C.Customer FROM [Customer] C
Left join Customer_Value V ON (C.Customer = V.Customer) and V.Value = '4'
WHERE V.Value is null
GROUP BY C.Customer
Your method is overkill; the JOIN is not necessary. I would use not exists:
select c.Customer
from Customer c
where not exists (select 1
from customer_value cv
where c.Customer = v.Customer and
cv.value = 4
);
You can also use aggregation, if you assume that all customers have at least one row in customer_value:
select cv.customer
from customer_value cv
group by cv.customer
having sum(case when cv.value = 4 then 1 else 0 end) = 0;
I would do
select * from customer c
join (
select distinct customer from customer_value where value!=4) v on c.customer = v.customer

Use STUFF with INNER JOIN Query

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/

Get nth level on self-referencing table

I have a self-referencing table which has at max 5 levels
groupid | parentid | detail
--------- | --------- | ---------
Group A | Highest | nope
Group B | Group A | i need this
Highest | NULL | nope
Group C | Group B | nope
Group D | Group C | nope
I have a transaction table which lookups to the groupid on the table above to retrieve the detail value where groupid = Group B. The values of the groupid on the transaction table is only between Group B to D and will never go any higher.
txnid | groupid | desired | desired
--------- | --------- | --------- | ---------
1 | Group D | Group B | i need this
2 | Group B | Group B | i need this
3 | Group C | Group B | i need this
4 | Group B | Group B | i need this
How should my T-SQL script be like to attain the desired column? I can left join to the self referencing table multiple times to get until group B it's not consistent on how many time I need to join back.
Greatly appreciate any thoughts!
Still not clear to me how do you know which is the GROUP B, I suppose it's the record where the parent of it parent is null.
create table org(groupid char(1), parentid char(1), details varchar(20));
insert into org values
('a', null, 'nope'),('b', 'a', 'I need this'),('c', 'b', 'nope'),('d', 'c', 'nope'),('e', 'd', 'nope');
create table trans(id int, groupid char(1));
insert into trans values
(1, 'b'),(2, 'c'),(3, 'c'),(4, 'd'),(5, 'e');
GO
10 rows affected
with all_levels as
(
select ob.groupid groupid_b, oc.groupid groupid_c,
od.groupid groupid_d, oe.groupid groupid_e,
ob.details
from org ob
inner join org oc
on oc.parentid = ob.groupid
inner join org od
on od.parentid = oc.groupid
inner join org oe
on oe.parentid = od.groupid
where ob.parentid is not null
) select * from all_levels;
GO
groupid_b | groupid_c | groupid_d | groupid_e | details
:-------- | :-------- | :-------- | :-------- | :----------
b | c | d | e | I need this
--= build a 4 levels row
with all_levels as
(
select ob.groupid groupid_b, oc.groupid groupid_c,
od.groupid groupid_d, oe.groupid groupid_e,
ob.details
from org ob
inner join org oc
on oc.parentid = ob.groupid
inner join org od
on od.parentid = oc.groupid
inner join org oe
on oe.parentid = od.groupid
where ob.parentid is not null
)
--= no matter what groupid returns b group details
, only_b as
(
select groupid_b as groupid, groupid_b, details from all_levels
union all
select groupid_c as groupid, groupid_b, details from all_levels
union all
select groupid_d as groupid, groupid_b, details from all_levels
union all
select groupid_e as groupid, groupid_b, details from all_levels
)
--= join with transactions table
select id, t.groupid, groupid_b, ob.details
from trans t
inner join only_b ob
on ob.groupid = t.groupid;
GO
id | groupid | groupid_b | details
-: | :------ | :-------- | :----------
1 | b | b | I need this
2 | c | b | I need this
3 | c | b | I need this
4 | d | b | I need this
5 | e | b | I need this
dbfiddle here
You can deal with a recursive function too, but I don't believe it can be better on terms of performance.
create function findDetails(#groupid char(1))
returns varchar(100)
as
begin
declare #parentid char(1) = '1';
declare #next_parentid char(1) = '1';
declare #details varchar(100) = '';
while #next_parentid is not null
begin
select #details = org.details, #parentid = org.parentid, #next_parentid = op.parentid
from org
inner join org op
on op.groupid = org.parentid
where org.groupid = #groupid
set #groupid = #parentid;
end
return #details;
end
GO
✓
select id, groupid, dbo.findDetails(groupid) as details_b
from trans;
GO
id | groupid | details_b
-: | :------ | :----------
1 | b | I need this
2 | c | I need this
3 | c | I need this
4 | d | I need this
5 | e | I need this
dbfiddle here