I have three tables:
____________________ ____________________ ____________________
posts tags posts_x_tags
____________________ ____________________ ____________________
| id | title | body | id | tag_name | post_id | tag_id
posts and tags have a many to many relationship.
Is is possible to do a search for posts.body OR posts.title OR tags.tag_name.
This gets me close but returns so many duplicates:
SELECT * FROM posts p
INNER JOIN posts_x_tags x ON p.id = x.post_id
INNER JOIN tags t ON t.id = x.tag_id
AND t.tag LIKE '%a%'
OR p.title LIKE '%a%'
OR p.body LIKE '%a%';
Any help would be much appreciated.
You can use SELECT DISTINCT ... in order to remove duplicates from result set.
Related
Say I have a table with schema as follows
id | name | tags |
1 | xyz | [4, 5] |
Where tags is an array of references to ids in another table called tags.
Is it possible to join these tags onto the row? i.e. replacing the id numbers with the values for thise rows in the tags table such as:
id | name | tags |
1 | xyz | [[tag_name, description], [tag_name, description]] |
If not, I wonder if this an issue with the design of the schema?
Example tags table:
create table tags(id int primary key, name text, description text);
insert into tags values
(4, 'tag_name_4', 'tag_description_4'),
(5, 'tag_name_5', 'tag_description_5');
You should unnest the column tags, use its elements to join the table tags and aggregate columns of the last table. You can aggregate arrays to array:
select t.id, t.name, array_agg(array[g.name, g.description])
from my_table as t
cross join unnest(tags) as tag
join tags g on g.id = tag
group by t.id;
id | name | array_agg
----+------+-----------------------------------------------------------------
1 | xyz | {{tag_name_4,tag_description_4},{tag_name_5,tag_description_5}}
(1 row)
or strings to array:
select t.id, t.name, array_agg(concat_ws(', ', g.name, g.description))
...
or maybe strings inside a string:
select t.id, t.name, string_agg(concat_ws(', ', g.name, g.description), '; ')
...
or the last but not least, as jsonb:
select t.id, t.name, jsonb_object_agg(g.name, g.description)
from my_table as t
cross join unnest(tags) as tag
join tags g on g.id = tag
group by t.id;
id | name | jsonb_object_agg
----+------+------------------------------------------------------------------------
1 | xyz | {"tag_name_4": "tag_description_4", "tag_name_5": "tag_description_5"}
(1 row)
Live demo: db<>fiddle.
not sure if this is still helpful for anyone, but unnesting the tags is quite a bit slower than letting postgres do the work directly from the array. you can rewrite the query and this is generally more performant because the g.id = ANY(tags) is a simple pkey index scan without the expansion step:
SELECT t.id, t.name, ARRAY_AGG(ARRAY[g.name, g.description])
FROM my_table AS t
LEFT JOIN tags AS g
ON g.id = ANY(tags)
GROUP BY t.id;
I have four tables to join and I need to identify which table the data is coming from in a column within the results. I join Tbl1 to Tbl2 using left outer join. I join Tbl1 to Tbl3 using left outer join and same with Tbl1 to Tbl 4. SerialNo is the key field I join all tables by. The results needs to indicate which table the data came from. For matching results between tables I want the right table to be identified. For example, in my sample tables I want to show that Tbl2 is in the results for those records where SerialNo is ABC123, DEF987 and HJK321.
Because of how the data will be extract from the database I'm unable to initiate a stored procedure so I'm planning on having a view to pull the data from, unless I can utilize a temp table in the process.
Tbl1
*Hostname* | *SerialNo*
Laptop1 | ABC123
Laptop2 | DEF987
Desktop1 | WER987
Desktop2 | YRT848
Desktop3 | YTT876
Laptop2 | HJK321
Tbl2
*Location* | *SerialNo*
MS | ABC123
CO | DEF987
CA | ZYC342
AZ | XYZ789
IN | HJK321
What I'd like to see in the results...
Result1
*Hostname* | *SerialNo* |*Location* |*RecordOrigin*
Laptop1 | ABC123 |MS |Tbl2
Laptop2 | DEF987 |CO |Tbl2
Desktop1 | WER987 |NULL |Tbl1
Desktop2 | YRT848 |NULL |Tbl1
Desktop3 | YTT876 |NULL |Tbl1
Laptop2 | HJK321 |IN |Tbl2
I tried creating an additional table for RecordOrigin information, but I wasn't able to join to the other tables correctly.
I'm should also note that I'm not able to edit the data in, or change the structure of, the source tables tables (i.e. Tbl1, Tbl2, etc.).
You can use a case expression to see if a table is returning a column or not like so:
select
t.Hostname
, t.SerialNo
, l.Location
, RecordOrigin = case
when l.SerialNo is not null
then 'Tbl2'
else 'Tbl1'
end
from Tbl1 as t
left join Tbl2 as l
on t.SerialNo = l.SerialNo
I think I might have figured it out by UNION the tables and using DISTINCT and MAX to remove duplication.
SELECT DISTINCT Hostname, MAX(SerialNo), MAX(Location), RecordOrigin
FROM
(
SELECT Hostname, SerialNo, Location, 'Tbl1' AS RecordOrigin
FROM Tbl1
UNION
SELECT Hostname, SerialNo, Location, 'Tbl2' AS RecordOrigin
FROM Tbl1
)
GROUP BY Hostname, RecordOrigin
This question already has answers here:
Difference between IS NULL criteria in JOIN and WHERE in a query
(2 answers)
Closed 6 years ago.
I want a report that show me which HumanResources.Employee weren't in relation with Sales.OrderHeader. I must use RIGHT/LEFT OUTER JOIN but in filtering we have two choise(use where statement in join or in where experssion).
I used this code:
SELECT
E.EmployeeID,
E.FirstName,
E.LastName,
O.OrderHeaderID,
O.OrderDate
FROM
Sales.OrderHeader O
RIGHT OUTER JOIN
HumanResources.Employee E ON O.EmployeeID = E.EmployeeID
AND O.OrderHeaderID IS NULL
and:
SELECT
E.EmployeeID,
E.FirstName,
E.LastName,
O.OrderHeaderID,
O.OrderDate
FROM
Sales.OrderHeader O
RIGHT OUTER JOIN
HumanResources.Employee E ON O.EmployeeID = E.EmployeeID
WHERE
O.OrderHeaderID IS NULL
I expected show me same result but first code shown:
EmployeeID| FirstName| LastName| OrderHeaderID
-----------+----------+------------+-----------
1 | نوری | محمد | NULL
2 | فربدی | حسن | NULL
3 | صباغی | محمد | NULL
4 | مرادی | احمد | NULL
10 | احمدی | حسن | NULL
11 | بیرونی | ساسان | NULL
and second code show this result (correct result):
EmployeeID| FirstName| LastName| OrderHeaderID
-----------+----------+------------+-----------
10 | احمدی | حسن | NULL
11 | بیرونی | ساسان | NULL
I didn't find that what reasons are behind these results.
Your codes are different. Second does what you described. Try to join based on employeId (records are in relation when they have same EmployeeID) then filter only results where right side is empty (no relation).
First code is trying to do the join based on equal EmployeeID and O.OrderHeaderID IS NULL. So you're saying that relation is when records have same EmployeeID and OrderHeaderID is null in table. So it is different relation.
First, I think LEFT JOIN is easier for most people to follow. It means "keep all the rows in the first table, along with matching values from the second". I admit that for people whose native language reads from right to left, this might be less natural.
Second, the ON clause in the outer join is very easy to follow. It says to keeps all the rows, even when there is no match. So, your condition is O.OrderHeaderID IS NULL. All the rows are kept in the second table (Employee), regardless of how this evaluates.
You know that the right solution is to put the condition in the WHERE clause, so there is no need to go over that.
T-SQL
Imagine two tables looking like this:
Table: students
==============================
| TeacherID | SName |
| 1 | Thompson |
| 1 | Nickles |
| 2 | Cree |
==============================
Table: teacher
====================================================
| TeacherID | TName | + many other fields |
| 1 | Pipers | |
| 2 | Slinger | |
====================================================
The field names are completely arbitrary.
I want to create a query with the following output:
================================================================
| TeacherName | many other fields | Students |
| Pipers | | Thompson,Nickles |
================================================================
Currently I have something like this:
SELECT *
FROM teacher
LEFT JOIN (
SELECT DISTINCT
EL2.teacherID,
STUFF(( SELECT ',' + SName
FROM students
WHERE EL2.teacherID = students.teacherID
FOR XML PATH('')
),1,1,'') AS "Students"
FROM students, teacher EL2) t1
ON t1.teacherID = teacher.teacherID
WHERE t1.Students LIKE '%Thompson%'
This works and gives me what I need. The WHERE clause is to illustrate that I
also absolutely need to be able to filter if a teacher has that student, but then put all students that teacher has into the concated field.
My question now is if there is a better way to do this.
I already looked at this:
Concatenate many rows into a single text string?
But it didn't help me much because one I couldn't get it to work with two seperate tables and two I couldn't filter the way I needed.
The SQL Management Studio execution plan indicates that the SELECT DISTINCT is
very expensive and others have said that the reliance on XML PATH is not optimal because it's behaviour can change.
Be carefull with a DISTINCT on names, as you might have two students with the same name! And btw: GROUP BY is in most cases a better performing approach to get a distinct list...
You might try something like this:
SELECT t.*
,STUFF(( SELECT ',' + s.SName
FROM students AS s
WHERE t.teacherID = s.teacherID
FOR XML PATH('')
),1,1,'') AS Students
FROM teacher AS t
WHERE EXISTS(SELECT 1 FROM students AS x WHERE x.teacherID=t.teacherID /*AND [PUT YOUR FILTER HERE]*/)
If I understand this correctly you want to find only teachers where one given student is connected to the teacher. And in this case you want to find all students bound to all teachers connected to the given student, correct?
At the end you find a /*AND [PUT YOUR FILTER HERE]*/ At this place you should put something like AND x.StudentId=123. This will filter the teachers to the rows connected with this student only. For these teachers all students are concatenated...
Use XML Path,..How for XML path works:
select
TeacherID,
Tname,
stuff((select ','+s.sname from students s where s.teacherid=t.teacherid
for xml path('')),1,1,'')as students
from
teachers t
I want to do a simple join of two tables in the same DB.
The expected result is:
To get all Node_ID's From the Table T_Tree that are the same as the TREE_CATEGORY from the Table T_Documents
My T_Documents Tabel:
+--------+----------------+---------------------+
| Doc_ID | TREEE_CATEGORY | Desc |
+--------+----------------+---------------------+
| 89893 | 1363 | Test |
| 89894 | 1364 | with a tab or 4 spa |
+--------+----------------+---------------------+
T_Tree Tabel
+----------+-------+
| Node_ID | Name |
+----------+-------+
| 89893 | Hallo |
| 89894 | BB |
+----------+-------+
Doc_ID is the primary key in the T_Documents Table and Tree_Category is the Foreign key
Node_ID is the primary key in the T_Tree Tabel
SELECT DBName.dbo.T_Tree.NODE_ID
FROM DBName.dbo.T_Documents
inner join TREE_CATEGORY on T_Documents.TREE_CATEGORY = DBName.dbo.T_Tree.NODE_ID
I can not figure it out how to do it correctly .. is this even the right approach ?
You were close. Try this:
SELECT t2.NODE_ID
FROM DBName.dbo.T_Documents t1
INNER JOIN DBName.dbo.T_Tree t2
ON t1.Doc_ID = t2.NODE_ID
Comments:
I used aliases in the query, which are a sort of shorthand for the table names. Aliases can make a query easier to read because it removes the need to always list full table names.
You need to specify table names in the JOIN clause, and the columns used for joining in the ON clause.
Your SQL should be:
SELECT DBName.dbo.T_Tree.NODE_ID
FROM DBName.dbo.T_Documents d
inner join T_Tree t on d.Doc_ID = t.Node_ID
Remember: you join relations (tables), not fields.
Also, for it to work, you need to have common values on Node_ID and Doc_ID fields. That is, for each value in Doc_ID of T_Documents table there must be an equal value in Node_ID field of T_Tree table.