I want to join 2 tables but there is no join table between them... I usually use STRING_SPLIT but in this case, I can't figure it out. Maybe I'm just tired... Could you help me please ?
CREATE TABLE ##Provider
(
id INT,
p_name VARCHAR(50),
list_id_dep VARCHAR(250)
)
CREATE TABLE ##Department
(
id INT,
d_name VARCHAR(50)
)
INSERT INTO ##Provider (id, p_name, list_id_dep) VALUES
(1, 'toto', '/10/11/12/'),
(2, 'tata', '/09/');
INSERT INTO ##Department (id, d_name) VALUES
(9, 'dep9')
,(10, 'dep10')
,(11, 'dep11')
,(12, 'dep12');
What I want is :
id | p_name | d_name
--------------------------
1 | toto | dep10
1 | toto | dep11
1 | toto | dep12
2 | tata | dep09
I've tried :
select *
from ##Provider p
inner join ##Department d on STRING_SPLIT(p.list_id_dep, '/') = ???
select *
from ##Provider p
inner join STRING_SPLIT(p.list_id_dep, '/') dep ON dep.value = ???
select *
from ##Provider p, ##Department d
where (select value from STRING_SPLIT(p.list_id_dep, '/')) = d.id
select *
from ##Provider p, ##Department d
where d.id in (select value from STRING_SPLIT(p.list_id_dep, '/'))
Maybe STRING_SPLIT is not the right way to do it...
Thanks !
You need a lateral join to unnest the string - in SQL Server, this is implented with cross apply. Then, you can bring the department table with a regular join:
select p.id, p.p_name, d.d_name
from ##provider p
cross apply string_split(p.list_id_dep, '/') x
inner join ##department d on d.id = x.value
Demo on DB Fiddle:
id | p_name | d_name
-: | :----- | :-----
1 | toto | dep10
1 | toto | dep11
1 | toto | dep12
2 | tata | dep9
Related
I have these tables:
names
id | name
7 | 'a'
8 | 'b'
9 | 'c'
group_names
id | group_of_names
1 | '7'
2 | '9,8'
3 | '7,8,9'
how to build a select that returns their names instead, separated by semicolon, preserving the original order as in:
id | single_text
1 | 'a'
2 | 'c;b'
3 | 'a;b;c'
I managed to do
select g.id, string_to_array(g.group_of_names,',')::int[] from group_names g;
id | string_to_array
1 | {7}
2 | {9,8}
3 | {7,8,9}
but i don't know how to, returning several arrays, for each of them, concatenate texts based on their ids
If the order of resulting strings is irrelevant:
select g.id, string_agg(n.name, ';')
from group_names g
join names n
on n.id = any(string_to_array(g.group_of_names, ',')::int[])
group by g.id
otherwise:
select g.id, string_agg(n.name, ';' order by ord)
from names n
join (
select id, elem, ord
from group_names
cross join regexp_split_to_table(group_of_names, ',')
with ordinality as arr(elem, ord)
) g
on n.id = g.elem::int
group by g.id
Test it in db<>fiddle.
In Postgres 14+ you can use string_to_table() instead of regexp_split_to_table().
You can try this way: Using the ANY operator to check if n.id value is in the array of group_of_names.
SELECT gn.id, string_agg(n.name, ';') AS single_text
FROM names n
INNER JOIN group_names gn ON n.id::text = ANY(string_to_array(gn.group_of_names, ','))
GROUP BY gn.id
ORDER BY gn.id;
Or this way: using your query and unnest() function to expand an array to a set of rows.
SELECT gn.id, string_agg(n.name, ';')
FROM names n
INNER JOIN (SELECT g.id, unnest(string_to_array(g.group_of_names, ',')::int[]) AS name_id
FROM group_names g) AS gn ON n.id = gn.name_id
GROUP BY gn.id
ORDER BY gn.id;
SELECT g.id, string_agg(n.name,';') AS single_text
FROM group_names AS g
CROSS JOIN LATERAL regexp_split_to_table (g.group_of_names, ',') AS gn(element)
INNER JOIN names AS n
ON n.id :: text = gn.element
GROUP BY g.id
I have 2 connected tables: Producto and Productos_ProductosRelacionados
Producto Productos_ProductosRelacionados
|id|referencia| |id|idProducto|idProductoRelacionado|
|1 | A | |1 | 1 | 2 |
|2 | B | |2 | 1 | 3 |
|3 | C | |3 | 3 | 4 |
|4 | D | |4 | 3 | 5 |
|5 | E |
I need this:
|idProducto|referencia|
| 1 | B,C |
| 2 | |
| 3 | D,E |
I have older SQL server so can not use STRING_AGG.
So far I only achieve concatenating idProductoRelacionado:
|idProducto|idProductoRelacionado|
| 1 | 2,3 |
with:
SELECT pr1.idProducto
,STUFF((
SELECT ',' + CONVERT(varchar, pr.idProductoRelacionado)
FROM [Productos_ProductosRelacionados] as pr
WHERE pr.idProducto = pr1.idProducto
FOR XML PATH('')), 1, 1, '') as RelacionadosID
FROM [dbo].[Producto] as p1
join [Productos_ProductosRelacionados] as pr1 on p1.id = pr1.idProductoRelacionado
GROUP BY pr1.idProducto
If I try the same approach to concatenate referencia column it gives me: "Column 'dbo.Producto.id' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause."
select pr1.idProducto
,STUFF((
SELECT ',' + p.referencia
FROM [dbo].[Producto] as p
WHERE p.id = p1.id
FOR XML PATH('')), 1, 1, '') as RelacionadosREF
from [dbo].[Producto] as p1
join [Productos_ProductosRelacionados] as pr1 on p1.id = pr1.idProductoRelacionado
GROUP BY pr1.idProducto
I do not understand the difference between 2 queries, why first one is working and second is not.
Simple Join should work. Check SQLFiddle
SELECT
id,
(SELECT
cast (p1.referencia as varchar(100)) + ','
FROM producto p
LEFT JOIN Productos_ProductosRelacionados pr
on pr.idProduct = p.id
LEFT JOIN producto p1
on p1.id = pr.idProductoRelacionado
WHERE p.id = src.id
FOR XML PATH('')) as referencia
FROM
producto src;
I ran into this a while back. You have to add a join to your stuff statement:
create table #prod (
id int,
ref varchar(1)
)
insert into #prod values
(1,'A'),
(2,'B'),
(3,'C'),
(4,'D'),
(5,'E')
create table #prod_rel (
id int,
pid int,
id_rel int
)
insert into #prod_rel values
(1,1,2),
(2,1,3),
(3,3,4),
(4,3,5)
select distinct
pid
,STUFF(
(SELECT ',' + c.ref FROM #prod_rel b inner join #prod c on c.id = b.id_rel where b.pid = a.pid FOR XML PATH ('')), 1, 1, ''
)
from #prod_rel a
drop table #prod
,#prod_rel
You can use recursive query method, like this:
--Create tables for example
select * into #Producto from (
select 1 id,'A' referential union all select 2,'B' union all select 3,'C' union all select 4,'D' union all select 5,'E'
) tmp;
select * into #Productos_ProductosRelacionados from (
select 1 id,1 idProducto,2 idProductoRelacionado union all select 2,1,3 union all select 3,3,4 union all select 4,3,5
) tmp;
-- Recurse query
With tmp as (
select ROW_NUMBER() over(partition by f1.ID order by f1.id, f3.referential desc) RangID,
count(*) over(partition by f1.ID order by f1.id) NbID,
f1.id, f3.referential
from #Producto f1
left outer join #Productos_ProductosRelacionados f2 on f1.id=f2.idProducto
left outer join #Producto f3 on f2.idProductoRelacionado=f3.id
),
Recurse as (
select f1.id, f1.RangID, f1.NbID, cast(f1.referential as varchar(2000)) referential, 1 rangrecur from tmp f1 where RangID=1
union all
select f1.id, f1.RangID, f1.NbID, cast(isnull(f1.referential, '') + ',' + isnull(f2.referential, '') as varchar(2000)) referential, f2.rangrecur + 1 as rangrecur
from tmp f1 inner join Recurse f2 on f1.id=f2.id and f1.RangID-1=f2.RangID
)
select ID, Referential from recurse
where NbID=rangrecur
order by ID;
I'm looking for a solution to particular query problem. I have a table Departments and table Employees designed like that:
Departments Employees
===================== ============================
ID | Name ID | Name | Surname | DeptID
--------------------- ----------------------------
1 | ADMINISTRATION 1 | X | Y | 2
2 | IT 2 | Z | Z | 1
3 | ADVERTISEMENT 3 | O | O | 1
4 | A | B | 3
I'd like to get list of all departments whose number of employees is smaller than number of employees working in Administration.
That was one of my ideas, but it did not work:
select * from Departments as Depts where Depts.ID in
(select Employees.ID from Employees group by Employees.ID
having count(Employees.ID) < count(case when Depts.Name='ADMINISTRATION' then 1 end));
Using GROUP BY and HAVING:
SELECT
d.ID, d.Name
FROM Departments d
LEFT JOIN Employees e
ON e.DeptID = d.ID
GROUP BY d.ID, d.Name
HAVING
COUNT(e.ID) < (SELECT COUNT(*) FROM Employees WHERE DeptID = 1)
Try this,
declare #Departments table (ID int, Name varchar(50))
insert into #Departments
values
(1 ,'ADMINISTRATION')
,(2 ,'IT')
,(3 ,'ADVERTISEMENT')
declare #Employees table (ID int, Name varchar(50)
,Surname varchar(50),DeptID int)
insert into #Employees
values
(1 ,'X','Y',2)
,(2 ,'Z','Z',1)
,(3 ,'O','O',1)
,(4 ,'A','B',3)
;
WITH CTE
AS (
SELECT *
,row_number() OVER (
PARTITION BY deptid ORDER BY id
) rn
FROM #Employees
WHERE deptid <> 1
)
SELECT *
FROM cte
WHERE rn < (
SELECT count(id) admincount
FROM #Employees
WHERE DeptID = 1
)
I have a table with two columns schema as:
ID1,ID2
Values are as:
x y
y x
a b
b a
I just want resultset as a whole like:
x y
a b
I want to remove duplicate
Need the sql query for the same.
One of the ways to achieve this is to use LEFT JOIN like this.
SQL Fiddle
Query
SELECT T1.ID1,T1.ID2
FROM YourTable T1
LEFT JOIN YourTable T2
ON T1.ID1 = T2.ID2
AND T1.ID2 = T2.ID1
AND T2.ID1 < T2.ID2
WHERE T2.ID1 IS NULL
Output
| ID1 | ID2 |
|-----|-----|
| x | y |
| a | b |
I suggest you to use EXISTS like this:
SELECT *
FROM yourTable t
WHERE NOT EXISTS(SELECT 1 FROM yourTable ti
WHERE t.ID1 = ti.ID2 AND t.ID2 = ti.ID1
AND ti.ID1 > ti.ID2)
Following structure exists:
CREATE TABLE rel(
entry_id int,
parent_id int
)
CREATE TABLE entries(
entry_id int,
name varchar(44)
)
Following data exists:
INSERT INTO entries VALUES (1,'user 1');
INSERT INTO entries VALUES (2,'group 2');
INSERT INTO entries VALUES (3,'group 3');
INSERT INTO entries VALUES (4,'user 4');
INSERT INTO entries VALUES (5,'user 5');
INSERT INTO rel VALUES (3,2);
INSERT INTO rel VALUES (4,2);
INSERT INTO rel VALUES (1,3);
INSERT INTO rel VALUES (5,3);
INSERT INTO rel VALUES (2,NULL);
Result should look like:
group_id| group_name | member_id | member_name | level
2 | group 2 | 4 | user 4 | 0
2 | group 2 | 1 | user 1 | 1
2 | group 2 | 5 | user 5 | 1
3 | group 3 | 1 | user 1 | 0
3 | group 3 | 5 | user 5 | 0
I already tried stuff like the following but it's not returning the results I need:
SELECT
entries.entry_id,
entries.name,
rel.parent_id,
(SELECT name FROM entries WHERE entry_id=parent_id) AS parent_name
INTO
#tmpEntries
FROM
entries, rel
WHERE
rel.entry_id = entries.entry_id
;
SELECT * FROM #tmpEntries;
WITH MyCTE
AS (
SELECT
entry_id,
name,
parent_id,
--CAST('' AS VARCHAR(44)) AS
parent_name
FROM #tmpEntries
--WHERE parent_id IS NULL
UNION ALL
SELECT
#tmpEntries.entry_id,
#tmpEntries.name,
#tmpEntries.parent_id,
--MyCTE.name AS
#tmpEntries.parent_name
FROM #tmpEntries
INNER JOIN MyCTE ON #tmpEntries.parent_id = MyCTE.entry_id
--WHERE #tmpEntries.parent_id IS NOT NULL
-- WHERE NOT EXISTS (SELECT entry_id FROM rel WHERE parent_id=#tmpEntries.entry_id)
)
SELECT DISTINCT *
FROM MyCTE
ORDER BY parent_id
;
WITH MyCTE2
AS (
SELECT
entry_id,
name,
parent_id,
--CAST('' AS VARCHAR(44)) AS
parent_name
FROM #tmpEntries
--WHERE parent_id IS NULL
UNION ALL
SELECT
#tmpEntries.entry_id,
#tmpEntries.name,
#tmpEntries.parent_id,
--MyCTE.name AS
#tmpEntries.parent_name
FROM #tmpEntries
INNER JOIN MyCTE2 ON #tmpEntries.parent_id = MyCTE2.entry_id
--WHERE #tmpEntries.parent_id IS NOT NULL
WHERE NOT EXISTS (SELECT entry_id FROM rel WHERE parent_id=#tmpEntries.entry_id)
)
SELECT DISTINCT *
FROM MyCTE2
ORDER BY parent_id
This will work under assumption that group is anything that contains one or more members i.e. empty group will be considered a simple member.
with cte_hierarchy as
(
select entry_id, parent_id, 0 as level
from rel
where parent_id is not null
union all
select h.entry_id, rel.parent_id, h.level + 1 as level
from cte_hierarchy h
inner join rel on h.parent_id = rel.entry_id
)
select
g.entry_id as group_id,
g.name as group_name,
e.entry_id as member_id,
e.name as member_name,
h.level
from cte_hierarchy h
inner join entries e on e.entry_id = h.entry_id
inner join entries g on g.entry_id = h.parent_id
where not exists (select * from rel where parent_id = h.entry_id)
order by g.entry_id, h.level, e.entry_id
The where clause excludes nested groups that would otherwise appear as members.