How to select record matchin all parameters? - sql

I have table catalog_view. I contains contacts with assigned categories. Contacts can have several categories. I want to select contacts, which belongs to all selected by user categories
e.g contact "Jone" belongs to categories [1,2,3,4,5,6] and contact "Jay" belongs to categories [1,3]. If user select categories [2,3], only "Jone" should be returned, if [1]|[3]|[1,3] selected, both should be returned, if [7]|[1,7] selected, none should be returned.
How should I build query and/or change tables to achive it?
query wich selects contacts by single category
SELECT * FROM catalog_view WHERE (category_id = ?)
this query selects record if assigned categories matches any of those and returns number of contacts rows up to quantity of parameters, also they are not force celection to all of them.
SELECT * FROM catalog_view WHERE (category_id = ? OR category_id = ? OR category_id =?)
this query always return empty table, as row in current template for catalog_view has only one category_id
SELECT * FROM catalog_view WHERE (category_id = ? AND category_id = ? AND category_id =?)
contacts
CREATE TABLE IF NOT EXISTS contacts_table(_id integer primary key, title text default '',title_lower text default '',title_short text default '',photo blob, number text default '', note text default '', note_lower text default '', email text, partner int default '0', contact_id int default '0', current_user text default '', categories text default '', saved integer default '0' );
categories
CREATE TABLE IF NOT EXISTS categories_table(_id integer primary key, category_id integer, parent_id integer, name text, section_name text, current_user text, sub_name text default '' );
contacts with categories
CREATE TABLE IF NOT EXISTS contacts_categories_table(_id integer primary key, contact_id integer, category_id integer, current_user text );
catalog view contacts
CREATE VIEW IF NOT EXISTS catalog_view AS SELECT contacts_table._id AS _id, contacts_table.contact_id, contacts_table.title_lower, contacts_table.note_lower, contacts_table.current_user, contacts_table.saved, contacts_table.partner, contacts_table.title, contacts_table.email, contacts_table.title_short, contacts_table.number, contacts_table.note, contacts_table.categories, categories_table.section_name, contacts_table.contact_id, categories_table.parent_id, categories_table.category_id FROM contacts_categories_table INNER JOIN contacts_table ON(contacts_categories_table.contact_id = contacts_table.contact_id AND contacts_categories_table.current_user = contacts_table.current_user) LEFT OUTER JOIN categories_table ON(contacts_categories_table.category_id = categories_table.category_id AND contacts_categories_table.current_user = categories_table.current_user)

You can try something like:
SELECT distinct name from catalog_view s
where NUMBER_OF_CONTACT_SELECTION =
(SELECT count(*) from catalog_view t
where t.name = s.name and
category_id in(YOUR CONTACT SELECTION HERE IN FORMAT OF 1,3,4...))
so for 1,3 your query should be:
SELECT distinct name from catalog_view s
where 2 =
(SELECT count(*) from catalog_view t
where t.name = s.name and
category_id in(1,3))
for 1,2,4,7 it should be -> 4 = (.....IN(1,2,4,7))

Related

How to do an INNER JOIN without getting duplicate rows?

I have two tables. A jobs table (with job.user_id) and a users table with user.id
I am trying to select all jobs and include username from users table, but I'm getting double the number of results I should be:
async getAll(context: any) {
const all = await db.queryEntries(`
SELECT j.*, u.username FROM jobs as j, users as u
INNER JOIN jobs ON jobs.user_id = u.id ORDER BY j.created_at DESC
`);
context.response.body = all;
}
I get two of everything. How do I just add 'username' where job.user_id = user.id?
users:
CREATE TABLE users (
id string primary key,
email string unique,
hashed_password string,
created_at TEXT,
updated_at TEXT,
username TEXT,
contactme INTEGER DEFAULT 1,
phone TEXT
);
jobs:
CREATE TABLE jobs (
id STRING PRIMARY KEY,
user_id STRING NOT NULL,
title STRING NOT NULL,
description STRING NOT NULL,
pay STRING NOT NULL,
type STRING NOT NULL,
created_at TEXT,
updated_at TEXT,
contact TEXT
);
I had to remove the "FROM users":
SELECT j.*, u.username FROM jobs as j
INNER JOIN users u ON j.user_id = u.id ORDER BY j.created_at DESC
This produces only one result now.

Posgresql join two tables where the foriegn key is an array of ids

I am new to SQL and I have three table
Templates Table
CREATE TABLE templates (
template_id serial PRIMARY KEY,
template_name VARCHAR ( 15 ) UNIQUE NOT NULL,
FOREIGN KEY (developer_id) REFERENCES users(user_id),
FOREIGN KEY (category_id) REFERENCES categories(category_id),
tag_ids int[],
FOREIGN KEY (EACH ELEMENT OF tag_ids) REFERENCES tags(tag_id)
);
Categories Table
CREATE TABLE categories (
category_id serial PRIMARY KEY,
category_name VARCHAR ( 15 ) UNIQUE NOT NULL
);
Tags Table
CREATE TABLE tags (
tag_id serial PRIMARY KEY,
tag_name VARCHAR ( 100 ) NOT NULL,
);
I want to Select all templates where each template has a category object and a tags object.
Each template has one category but may have multiple tags.
I want to have the tags as an array attribute in the template object
I have tried this query, it does what i want but it creates multiple objects for the same template. So it simply creates n objects where n is the number of tags.
let query = `SELECT t.*, to_json(c) "category", ${developerJson} "developer", json_agg(tgs) "tags" FROM templates t INNER JOIN categories c ON t.category_id = c.category_id INNER JOIN users d ON t.developer_id = d.user_id JOIN tags tgs ON tgs.tag_id = ANY(t.tags_id) ${condition} ${groupBy}`;
Can anyone help me?
I have found the solution. I was passing the tag_id in the group elements.
Once I removed it, I got what I was expecting
const developerJson = `json_build_object( 'first_name',first_name, 'last_name', last_name, 'avatar_link', avatar_link, 'slug', d.slug ,'date_joined',date_joined)`;
const groupBy = `GROUP BY t.template_id, c.*, d.first_name, d.last_name, d.avatar_link, d.slug, d.date_joined`;
let query = `SELECT t.*, to_json(c) "category", ${developerJson} "developer", json_agg(tgs) "tags" FROM templates t INNER JOIN categories c ON t.category_id = c.category_id JOIN users d ON t.developer_id = d.user_id JOIN tags tgs ON tgs.tag_id = ANY(t.tags_id) ${groupBy}`;

How do I get average through ‏multiple tables

I got homework to get ‏average tags of user in album (user_id = x) in the folowing tabels:
>>> CREATE TABLE USERS (ID INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
NAME TEXT NOT NULL);
>>> CREATE TABLE ALBUMS (ID INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
NAME TEXT NOT NULL, CREATION_DATE TEXT NOT NULL,
USER_ID INTEGER REFERENCES USERS(USER_ID) NOT NULL);
>>> CREATE TABLE PICTURES (ID INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
NAME TEXT NOT NULL,
LOCATION TEXT NOT NULL,
CREATION_DATE TEXT NOT NULL,
ALBUM_ID INTEGER REFERENCES ALBUMS(ALBUM_ID) NOT NULL);
>>> CREATE TABLE TAGS (ID INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
PICTURE_ID INTEGER REFERENCES PICTURES(PICTURE_ID) NOT NULL,
USER_ID INTEGER REFERENCES USERS(USER_ID) NOT NULL);";
explenetion:
Each tag is a row in TAGS and it has picture_id, each picture has album_id and each album has user_id, basically i need to count how many times the user is tagged in each album and find the average times that the user is tagged in an album.
I can use this using only: SELECT ? FROM, AVG(), COUNT(), JOIN (INNER, LEFT, RIGHT, FULL JOIN), ON, IN, AND, OR, LIKE, , NOT, (=, != , >, <), IS, DISTINCT, ORDER BY(ASC/DESC), LIMT, OFFSET, and WHERE that means i cannot use GROUP BY
i tried this
SELECT * FROM TAGS INNER JOIN PICTURES ON tags.picture_id = PICTURES.Id where album_id IN (select id from ALBUMS where user_id = x) AND user_id = x;
but it only gives my a table that has all the tags of the user
How can i get the avg tags per album of (user_id = x), is this even possible?
First count how many times the user is tagged in each album and then get the average of these counters:
select
avg(counter) averagetags
from (
select count(t.user_id) counter
from albums a
inner join pictures p on p.album_id = a.id
inner join tags t on t.picture_id = p.id
where t.user_id = ?
group by a.id
)

postgres: (sub)select and combine optional content into an array

i have the following table structure:
Location----- * Media ----1 Attribute --------* AttributeTranslation
Each Location has n mediaitems attached, containing one optional attribute (text) and n associated translationa for that attribute.
I need to select this data into an array, so that i get for each location the associated medialist for each language.
what i currently do and what i get:
SELECT m.location_id, t.language_id,
array_agg_mult(
ARRAY[ARRAY[m.sortorder::text, m.filename, t.name]] ORDER BY m.sortorder
) as medialist
FROM Media m
LEFT JOIN ATTRIBUTE a ON a.id = m.attribute_id
LEFT JOIN AttributeTranslation t ON a.id = t.attribute_id
WHERE m.location_id = ?
GROUP BY m.location_id, t.language_id
This gives me following result for the given scenario: the current location has 4 images attached, only the first image has an associated attribute containing two translations:
Location_ID Language_ID MEDIALIST
AT_014 1 {{1,'location_image1.jpg','attribute german'}}
AT_014 2 {{1,'location_image1.jpg','attribute english'}}
AT_014 {{2,'location_image2.jpg',null},{3,'location_image3.jpg',null},{4,'location_image4.jpg',null}}
but what i need instead is this:
Location_ID Language_ID MEDIALIST
AT_014 1 {{1,'location_image1.jpg','attribute german'},{2,'location_image2.jpg',null},{3,'location_image3.jpg',null},{4,'location_image4.jpg',null}}
AT_014 2 {{1,'location_image1.jpg','attribute english'},{2,'location_image2.jpg',null},{3,'location_image3.jpg',null},{4,'location_image4.jpg',null}}
those 3 columns are part of a view, so that i can do later:
select * from locationview where location_id = ? and language_id = ?
how can i achieve the desired result here? thanks in advance!
Simplified Table Definitions:
CREATE TABLE LOCATION (
location_id numeric(20) primary key,
description text
);
CREATE TABLE MEDIA (
media_id numeric(20) primary key,
fileName text,
sortorder smallint,
location_id numeric(20) references LOCATION(location_id),
attribute_id numeric(20) references ATTRIBUTE(attribute_id)
);
CREATE TABLE ATTRIBUTE (
attribute_id numeric(20) primary key,
attributetype varchar(100),
);
CREATE TABLE ATTRIBUTETRANSLATION (
translation_id numeric(20),
language_id smallint,
name text,
description text,
attribute_id numeric(20) references ATTRIBUTE(attribute_id)
);
ALTER TABLE ATTRIBUTETRANSLATION add constraint AT_ID primary key(translation_id, language_id)
I am not sure I fully understand your question, but here's an attempt. You could take the output of your query, and match each row that has a language_id with the corresponding rows where language_id is NULL, so that you can then concatenate the medialist arrays. Here's a way to do that by creating an alias of your query with a CTE:
WITH t AS (
SELECT m.location_id, t.language_id,
array_agg(
ARRAY[ARRAY[m.sortorder::text, m.filename, t.name]] ORDER BY m.sortorder
) as medialist
FROM Media m
LEFT JOIN ATTRIBUTE a ON a.attribute_id = m.attribute_id
LEFT JOIN AttributeTranslation t ON a.attribute_id = t.attribute_id
WHERE m.location_id = ?
GROUP BY m.location_id, t.language_id
)
SELECT location_id, t1.language_id, t1.medialist || t2.medialist AS medialist
FROM (SELECT * FROM t WHERE language_id IS NOT NULL) t1
RIGHT OUTER JOIN (SELECT * FROM t WHERE language_id IS NULL) t2 USING (location_id);
I am not sure if this does exactly what you want, but hopefully it will give you some ideas.

Get rows that no foreign keys point to

I have two tables
CREATE TABLE public.city_url
(
id bigint NOT NULL DEFAULT nextval('city_url_id_seq'::regclass),
url text,
city text,
state text,
country text,
common_name text,
CONSTRAINT city_url_pkey PRIMARY KEY (id)
)
and
CREATE TABLE public.email_account
(
id bigint NOT NULL DEFAULT nextval('email_accounts_id_seq'::regclass),
email text,
password text,
total_replied integer DEFAULT 0,
last_accessed timestamp with time zone,
enabled boolean NOT NULL DEFAULT true,
deleted boolean NOT NULL DEFAULT false,
city_url_id bigint,
CONSTRAINT email_accounts_pkey PRIMARY KEY (id),
CONSTRAINT email_account_city_url_id_fkey FOREIGN KEY (city_url_id)
REFERENCES public.city_url (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION
)
I want to come up with a query that fetches rows in the city_url only if there is no row in the email_account pointing to it with the city_url_id column.
NOT EXISTS comes to mind:
select c.*
from city_url c
where not exists (select 1
from email_account ea
where ea.city_url_id = c.id
);
There's also this option:
SELECT city_url.*
FROM city_url
LEFT JOIN email_account ON email_account.city_url_id = city_url.id
WHERE email_account.id IS NULL
A NOT EXISTS is absolutely the answer to the "... if there is no row ...".
Nonetheless it would be preferable to accomplish this by selecting then difference quantity.
Which is in principle:
SELECT a.*
FROM table1 a
LEFT JOIN table2 b
ON a.[columnX] = b.[columnY]
WHERE b.[columnY] IS NULL
Using the tablenames here, this would be:
SELECT c.*
FROM city_url c
LEFT JOIN email_account e
ON c.id = e.city_url
WHERE e.city_url IS NULL
I believe NOT IN could be used here as well, although this might be less performant on large datasets:
SELECT *
FROM city_url
WHERE id NOT IN (
SELECT city_url_id FROM email_account
)