Query for a table where FK can be null - sql

A simple case: we have a main table (id, name, dictionary_id) and a dictionary table (id, name). The dictionary_id is the FK related with the dictionary table. NULL values are possible for dictionary_id.
I need to show:
id, name, dictionary_name
if dictionary_id is null (in this case dictionary_name is empty) or
dictionary_id comes from a list (e.g. from subquery).
Thanks,
Jacek

You are looking for an outer join:
select mt.id, mt.name, dt.name as dictionary_name
from main_table mt
left join dictionary_table dt on mt.dictionary_id = dt.id;

Related

SQL INSERT/SELECT using WITH clause

I'm trying to make a copy from two tables based on a list of IDs, those IDs are retrieved from an initial table, then those IDs are used into an INSERT SELECT statement, also the created IDs from the previous INSERT must be inserted into a third table:
BEGIN;
WITH dog_tmp AS (SELECT id FROM dog WHERE toy_id = '12345'),
meal_tmp as (INSERT INTO meal (id, dog_id, date_created, type)
SELECT public.uuid_generate_v4(), dt.id, m.date_created, m.type
FROM meal AS m
JOIN dog_tmp dt ON dt.id = m.dog_id RETURNING m.id AS meal_uuid)
INSERT INTO dog_diet (id, meal_id, date_created)
SELECT public.uuid_generate_v4(), mt.meal_uuid, dd.date_created
FROM meal_tmp mt
JOIN dog_diet dd ON dd.thread_id = mt.meal_uuid;
COMMIT;
I'm getting this error: [42P01] ERROR: missing FROM-clause entry for table "m"
Thanks in advance for your hints about this, or if possible another approach.
Quick answer: change RETURNING m.id AS meal_uuid to RETURNING id AS meal_uuid (db fiddle).
So the full statement at issue is:
INSERT INTO meal (id, dog_id, date_created, type)
SELECT public.uuid_generate_v4(), dt.id, m.date_created, m.type
FROM meal AS m
JOIN dog_tmp dt ON dt.id = m.dog_id
RETURNING m.id AS meal_uuid
Within the select you set m as an alias for meal but the scope of this is the SELECT query (so m is not defined within the outer insert statement).

How to use INTERSECT together with COUNT in SQLite?

I have a table called customer_transactions and a table called blacklist.
The customer_transactions table has a column called atm_name.
Both tables share a unique key called id.
How can I intersect the two tables in such a way that the query shows me
customers that appear on both tables.
a corresponding column that displays the times that they had used a
certain atm alongside the atm's name
(for instance: id_1 -- bank of america -- 2; id_1 -- citibank -- 3;
id_2 -- bank of america -- 1; id_2 -- citibank -- 4, etcetera).
I have something like this
SELECT id,
atm_name,
count(atm_name) as atm_count
FROM customer_transactions
GROUP BY id, atm_name
How can I INTERSECT this table with the blacklist table and maintain what I currently have as output?
Thanks in advance.
You seem to want a join. Assuming that column id relates the two tables, and that it is a unique key in blacklist, you can do:
select ct.id, ct.atm_name, count(*) as atm_count
from customer_transactions ct
inner join blacklist b on b.id = ct.id
group by ct.id, ct.atm_name
You can also express this logic with exists and a correlated subquery:
select ct.id, ct.atm_name, count(*) as atm_count
from customer_transactions ct
where exists (select 1 from blacklist b where b.id = ct.id)
group by ct.id, ct.atm_name

SQL join based on select as column name

So in one table I can use a replace and charindex to extract a specific ID that relates to a PK in another table, I want to then join the data from the other table based on the trimmed value, how can I do this?
select top 100 *, Replace(Left(LogValue,Charindex(';', LogValue) - 1) ,'RtaId=', '') as TaskID, PrmRoutingTask.*
from SytLog
inner join PrmRoutingTask on RtaId = TaskID
where LogTableName like '%Routing%' and LogValue like '%RtaAniId=397%'
order by 5 desc;
The problem I get is that the temp column name I create (TaskID) is not working in the inner join where in fact the results of TaskID have the reference to the RtaId in the RoutingTask table.
Assuming LogValue belongs to the first table you can use the column named TaskID if you produce a subquery as a table expression of the main query.
For example you can produce the column in the table expression a by doing:
select top 100
a.*,
PrmRoutingTask.*
from (
select *,
Replace(Left(LogValue,Charindex(';', LogValue) - 1) ,'RtaId=', '') as TaskID
from SytLog
) a
inner join PrmRoutingTask on PrmRoutingTask.RtaId = a.TaskID
where LogTableName like '%Routing%'
and LogValue like '%RtaAniId=397%'
order by 5 desc

what is the proper union or sql construct to resolve these 2 datasets?

I have a table UserParent:
Id, FirstName, LastName
I have a table UserChild:
Id, ParentUserId (FK), ChildAttributeX
I have the following sample SQL:
SELECT Id, 0 ChildUserId, FirstName, LastName, NULL FROM UserParent
UNION
SELECT ParentUserId, Id, FirstName, LastName, ChildAttributeX FROM UserChild
Some Users may exist in both tables. All Users are stored with basic info in UserParent although some Users who have ChildAttributeX will have a FK ref to the UserParent in UserChild along with the ChildAttributeX in UserChild.
How can I resolve this as part of a UNION or some other SQL technique so all Users are included in the result set, without duplicate users?
I think this is what you are looking for. If all records must exist in parent table, this will return all records from parent, and any record that exist in child table, but only unique records (DISTINCT does that).
SELECT DISTINCT UP.ID, UP.FirstName, UP.LastName
FROM UserParent UP
LEFT OUTER JOIN UserChild UC ON UP.ID = UC.ParentUserID
If you are looking for all the records present in both table, you can try below query:
SELECT
coalesce(UP.Id,UC.ParentUserId),
0 ChildUserId,
(UP.FirstName,UC.FirstName),
(UP.LastName,UC.LastName),
NULL FROM
UserParent UP
FULL OUTER JOIN
UserChild UC
ON UC.ParentUserId = UP.ID

SELECT from subquery without having to specify all columns in GROUP BY

Idea is to query an article table where an article has a given tag, and then to STRING_AGG all (even unrelated) tags that belong to that article row.
Example tables and query:
CREATE TABLE article (id SERIAL, body TEXT);
CREATE TABLE article_tag (article INT, tag INT);
CREATE TABLE tag (id SERIAL, title TEXT);
SELECT DISTICT ON (id)
q.id, q.body, STRING_AGG(q.tag_title, '|') tags
FROM (
SELECT a.*, tag.title tag_title
FROM article a
LEFT JOIN article_tag x ON a.id = tag.article
LEFT JOIN tag ON tag.id = x.tag
WHERE tag.title = 'someTag'
) q
GROUP BY q.id
Running the above, postgres require that the q.body must be included in GROUP BY:
ERROR: column "q.body" must appear in the GROUP BY clause or be used in an aggregate function
As I understand it, it's because subquery q doesn't include any PRIMARY key.
I naively thought that the DISTINCT ON would supplement that, but it doesn't seem so.
Is there a way to mark a column in a subquery as PRIMARY so that we don't have to list all columns in GROUP BY clause?
If we do have to list all columns in GROUP BY clause, does that incur significant perf cost?
EDIT: to elaborate, since PostgreSQL 9.1 you don't have to supply non-primary (i.e. functionally dependent) keys when using GROUP BY, e.g. following query works fine:
SELECT a.id, a.body, STRING_AGG(tag.title, '|') tags
FROM article a
LEFT JOIN article_tag x ON a.id = tag.article
LEFT JOIN tag ON tag.id = x.tag
GROUP BY a.id
I was wondering if I can leverage the same behavior, but with a subquery (by somehow indicating that q.id is a PRIMARY key).
It sadly doesn't work when you wrap your primary key in subquery and I don't know of any way to "mark it" as you suggested.
You can try this workaround using window function and distinct:
CREATE TABLE test1 (id serial primary key, name text, value text);
CREATE TABLE test2 (id serial primary key, test1_id int, value text);
INSERT INTO test1(name, value)
values('name1', 'test01'), ('name2', 'test02'), ('name3', 'test03');
INSERT INTO test2(test1_id, value)
values(1, 'test1'), (1, 'test2'), (3, 'test3');
SELECT DISTINCT ON (id) id, name, string_agg(value2, '|') over (partition by id)
FROM (SELECT test1.*, test2.value AS value2
FROM test1
LEFT JOIN test2 ON test2.test1_id = test1.id) AS sub;
id name string_agg
1 name1 test1|test2
2 name2 null
3 name3 test3
Demo
Problem is in outer SELECT - you should either aggregate columns either
group by them. Postgres wants you to specify what to do with q.body - group by it or calculate aggregate. Looks little bit awkward but should work.
SELECT DISTICT ON (id)
q.id, q.body, STRING_AGG(q.tag_title, '|') tags
FROM (
SELECT a.*, tag.title tag_title
FROM article a
LEFT JOIN article_tag x ON a.id = tag.article
LEFT JOIN tag ON tag.id = x.tag
WHERE tag.title = 'someTag'
) q
GROUP BY q.id, q.body
-- ^^^^^^
Another way is to make a query to get id and aggregated tags then join body to it. If you wish I can make an example.