Postgres Query to Generate a Table Matrix - postgresql

I am attempting to generate a "matrix" (I may be using the term incorrectly here) from 3 tables using a Postgres query.
How can I achieve this using such an SQL query?
Here are the example tables I have at the moment:
Company
+----+-------------+
| id | name |
+----+-------------+
| 1 | 9999999991 |
| 2 | 9999999992 |
| 3 | 9999999993 |
| 4 | 9999999994 |
| 5 | 9999999995 |
| 6 | 9999999996 |
| 7 | 9999999997 |
| 8 | 9999999998 |
+----+-------------+
Services
+----+-------------+
| id | name |
+----+-------------+
| 1 | Service 1 |
| 2 | Service 2 |
| 3 | Service 3 |
| 4 | Service 4 |
+----+-------------+
Service Company Map
+----+----------+---------------+
| id |company | services |
+----+-----------+--------------+
| 1 | 9999999991| 2 |
| 2 | 9999999991| 4 |
| 3 | 9999999992| 1 |
| 4 | 9999999992| 4 |
| 5 | 9999999993| 1 |
| 6 | 9999999993| 3 |
| 7 | 9999999993| 4 |
+----+-----------+--------------+
Here is an example of the matrix I am attempting to generate
+----------+----------+----------+----------+----------+
| | Service 1| Service 2| Service 3| Service 4|
+----------+----------+----------+----------+----------+
| Company 1| - | X | - | X |
| Company 2| X | - | - | X |
| Company 3| X | - | X | X |
| Company 4| - | - | - | - |
+----------+----------+----------+----------+----------+
(Note, I did reference this question, but we seem to be after different things: Postgres query for matrix table)
Update: Basic DDL / Inserts per #SQLpro's request
CREATE TABLE service_client_mappings
( id int NOT NULL,
company_id int NOT NULL,
service_id int NOT NULL,
CONSTRAINT service_client_mappings_pk PRIMARY KEY (id)
);
CREATE TABLE services
( id int NOT NULL,
name char(50) NOT NULL,
CONSTRAINT services_pk PRIMARY KEY (id)
);
CREATE TABLE company
( id int NOT NULL,
name char(50) NOT NULL,
CONSTRAINT company_pk PRIMARY KEY (id)
);
INSERT INTO company
(id, name)
VALUES
(1, 'ACME');
INSERT INTO company
(id, name)
VALUES
(2, 'Target');
INSERT INTO company
(id, name)
VALUES
(3, 'Walmart');
INSERT INTO services
(id, name)
VALUES
(1, 'Service A');
INSERT INTO services
(id, name)
VALUES
(2, 'Service B');
INSERT INTO services
(id, name)
VALUES
(3, 'Service C');
INSERT INTO services
(id, name)
VALUES
(4, 'Service D');
INSERT INTO service_client_mappings
(id, company_id, service_id)
VALUES
(1, 1, 2);
INSERT INTO service_client_mappings
(id, company_id, service_id)
VALUES
(1, 1, 4);
INSERT INTO service_client_mappings
(id, company_id, service_id)
VALUES
(1, 2, 1);
INSERT INTO service_client_mappings
(id, company_id, service_id)
VALUES
(1, 3, 2);
INSERT INTO service_client_mappings
(id, company_id, service_id)
VALUES
(1, 3, 3);

SELECT name,
CASE WHEN EXISTS(SELECT *
FROM services AS S
JOIN service_client_mappings AS m
ON S.id = m.service_id
WHERE s.name = 'Service A'
AND c.id = m.company_id)
THEN 'X'
ELSE '-'
END AS "Service A",
CASE WHEN EXISTS(SELECT *
FROM services AS S
JOIN service_client_mappings AS m
ON S.id = m.service_id
WHERE s.name = 'Service B'
AND c.id = m.company_id)
THEN 'X'
ELSE '-'
END AS "Service B",
CASE WHEN EXISTS(SELECT *
FROM services AS S
JOIN service_client_mappings AS m
ON S.id = m.service_id
WHERE s.name = 'Service C'
AND c.id = m.company_id)
THEN 'X'
ELSE '-'
END AS "Service C",
CASE WHEN EXISTS(SELECT *
FROM services AS S
JOIN service_client_mappings AS m
ON S.id = m.service_id
WHERE s.name = 'Service D'
AND c.id = m.company_id)
THEN 'X'
ELSE '-'
END AS "Service D"
FROM company AS c;
By the way your adata are incorrect... For INSERT INTO service_client_mappings, you should have :
INSERT INTO service_client_mappings
(id, company_id, service_id)
VALUES
(1, 1, 2);
INSERT INTO service_client_mappings
(id, company_id, service_id)
VALUES
(2, 1, 4);
INSERT INTO service_client_mappings
(id, company_id, service_id)
VALUES
(3, 2, 1);
INSERT INTO service_client_mappings
(id, company_id, service_id)
VALUES
(4, 3, 2);
INSERT INTO service_client_mappings
(id, company_id, service_id)
VALUES
(5, 3, 3);
Result is :
name Service A Service B Service C Service D
-------------- --------- --------- --------- ---------
ACME - X - X
Target X - - -
Walmart - X X -
In addition, to build this with a variable number of services you can use dynamic sql...
A first way to do this is :
SELECT CONCAT(
'SELECT name,
CASE WHEN EXISTS(SELECT *
FROM services AS S
JOIN service_client_mappings AS m
ON S.id = m.service_id
WHERE s.name = ''', name, '''
AND c.id = m.company_id)
THEN ''X''
ELSE ''-''
END AS "', name , '",') AS SQL_STRING
FROM services;

Related

How to bulk update while overriding "CREATE UNIQUE INDEX" constraint?

I have table
CREATE TABLE my_table (
ranking INTEGER NOT NULL,
name TEXT NOT NULL,
is_deleted BOOLEAN NOT NULL
);
CREATE UNIQUE INDEX exists_const ON my_table ranking WHERE (is_deleted = FALSE);
| ranking | name | is_deleted |
| 1 | A | true |
| 2 | B | true |
| 1 | C | true |
| 1 | D | false |
| 2 | E | false |
| 3 | F | false |
So when I want to update a new ranking for D, E, F to be
UPDATE "my-table" AS t SET "ranking"=v."ranking" FROM
(VALUES(1, 'F', 'false'),(2, 'D', 'false'), (3, 'E', 'false'))
AS v("ranking","name", 'is_deleted') WHERE v.name = t.name
| 1 | F | false |
| 2 | D | false |
| 3 | E | false |
I have duplicate key value violates unique constraint "exists_const" ERROR.
Even though the new rows will not have duplicates at all. How can I perform such update?
The problem can be solved by Deferring the check at the end of the transaction, but you cannot do it with INDEX (as far as I know). The workaround I can give is to use EXCLUDE as described below:
CREATE TABLE my_table (
ranking INTEGER NOT NULL,
name TEXT NOT NULL,
is_deleted BOOLEAN NOT NULL,
EXCLUDE USING BTREE (ranking WITH =) WHERE (not is_deleted) DEFERRABLE
);
Attention to the 'DEFERRABLE'
Here to populate initial data:
insert into my_table ( ranking, name, is_deleted)
values
(1, 'A', true),
(2, 'B', true),
(1, 'C', true),
(1, 'D', false),
(2, 'E', false),
(3, 'F', false);
Now if you execute :
UPDATE "my_table" AS t
SET "ranking" = v."ranking"
FROM
(VALUES(1, 'F', 'false'),(2, 'D', 'false'), (3, 'E', 'false'))
AS v("ranking","name", "is_deleted")
WHERE v.name = t.name;
It should work.
And to test the uniqueness I tested it with :
insert into my_table ( ranking, name, is_deleted)
values
(3, 'G', false);

Subquery as a JSON field

Using the hypothetical schema:
CREATE TABLE obj (id INT, name VARCHAR);
CREATE TABLE objprop (obj_id INT, key VARCHAR, value VARCHAR);
INSERT INTO obj VALUES
(1, 'Object 1'),
(2, 'Object 2'),
(3, 'Object 3');
INSERT INTO objprop VALUES
(1, 'created', '2020-02-16'),
(1, 'updated', '2020-02-28'),
(2, 'created', '2020-02-01');
Could I obtain a list of objects (one per row), and a JSON field that represents object's properties?
I know I can use the ARRAY() function with a subquery to retrieve an array of values, for example:
SELECT id, name, ARRAY(SELECT value FROM objprop where obj_id=id) values FROM obj;
+----+----------+------------------------------+
| id | name | values |
+----+----------+------------------------------+
| 1 | Object 1 | {'2020-02-16', '2020-02-28'} |
| 2 | Object 2 | {'2020-02-01'} |
| 3 | Object 3 | {} |
+----+----------+------------------------------+
But could I make a query that instead of an ARRAY, it would return me a JSON column with the subquery in it? My goal is to obtain for example:
+---+----------+----------------------------------------------------------------------------------------+
| 1 | Object 1 | [{"key": "created", "value": "2020-02-16"}, {"key": "updated", "value": "2020-02-28"}] |
| 2 | Object 2 | [{"key": "created", "value": "2020-02-01"}] |
| 3 | Object 3 | [] |
+---+----------+----------------------------------------------------------------------------------------+
SELECT
id,
name,
COALESCE((
SELECT json_agg(json_build_object('key', key, 'value', value))
FROM objprop where obj_id=id
), '[]'::json) vals
FROM
obj;

The objective is to display the most recent transaction by custmer_id

My code ranks the last customer transaction by row number as planned, but I cannot filter my join to display on the last transaction per customer. The objective is to display the last detailed customer transaction per customer_id. I attempted to use the window function and then filter the resulting column.
CREATE TABLE customer1 (
customer_id INT PRIMARY KEY,
first_name VARCHAR(255),
last_name VARCHAR(255),
email VARCHAR(255),
created_at TIMESTAMP WITH TIME ZONE NOT NULL
);
CREATE TABLE purchase (
purchase_id INT PRIMARY KEY,
purchase_time TIMESTAMP WITH TIME ZONE NOT NULL,
customer_id INT NOT NULL,
FOREIGN KEY (customer_id) REFERENCES customer1(customer_id)
);
CREATE TABLE purchase_item (
purchase_item_id INT PRIMARY KEY,
purchase_id INT NOT NULL,
sku VARCHAR(255),
quantity INT NOT NULL,
total_amount_paid DECIMAL(10,2) NOT NULL,
FOREIGN KEY (purchase_id) REFERENCES purchase(purchase_id)
);
INSERT INTO customer1 (customer_id, first_name, last_name, email, created_at) VALUES
(1, 'James', 'Smith', 'jamessmith#example.com', clock_timestamp()),
(2, 'Mary', 'Johnson', 'maryjohnson#example.com', clock_timestamp()),
(3, 'John', 'Williams', 'johnwilliams#example.com', clock_timestamp()),
(4, 'Patricia', 'Brown', 'patriciabrown#example.com', clock_timestamp()),
(5, 'Michael', 'Garcia', 'michaelgarcia#example.com', clock_timestamp());
INSERT INTO purchase (purchase_id, purchase_time, customer_id) VALUES
(100, clock_timestamp(), 1),
(101, clock_timestamp(), 1),
(102, clock_timestamp(), 1),
(103, clock_timestamp(), 2),
(104, clock_timestamp(), 3),
(105, clock_timestamp(), 5);
INSERT INTO purchase_item(purchase_item_id, purchase_id, sku, quantity, total_amount_paid) VALUES
(200, 100, 'shoe_blk_42', 3, 300),
(201, 100, 'shoe_lace_white', 3, 2.5),
(202, 101, 'shorts', 1, 40),
(203, 102, 'bike', 1, 1995),
(204, 103, 'bike', 2, 3990),
(205, 103, 'shoe_wht_39', 2, 200),
(206, 104, 'shirt', 1, 60),
(207, 105, 'headphones', 1, 400);
SELECT DISTINCT customer1.customer_id,
first_name,
last_name,
email,
purchase.purchase_id,
purchase.purchase_time,
purchase_item.quantity,
purchase_item.total_amount_paid,
ROW_NUMBER()OVER (
PARTITION BY purchase.customer_id
ORDER BY
purchase.purchase_time DESC
) As order_queue
FROM customer1
JOIN purchase ON customer1.customer_id = purchase.customer_id
JOIN purchase_item ON purchase.purchase_id = purchase_item.purchase_id
WHERE order_queue = 1;
You can use DISTINCT ON to solve this:
select distinct on (customer1.customer_id)
customer1.customer_id,
first_name,
last_name,
email,
purchase.purchase_id,
purchase.purchase_time,
purchase_item.quantity,
purchase_item.total_amount_paid
FROM customer1
LEFT JOIN purchase ON customer1.customer_id = purchase.customer_id
LEFT JOIN purchase_item ON purchase.purchase_id = purchase_item.purchase_id
ORDER BY customer1.customer_id, purchase_time desc;
customer_id | first_name | last_name | email | purchase_id | purchase_time | quantity | total_amount_paid
-------------+------------+-----------+---------------------------+-------------+-------------------------------+----------+-------------------
1 | James | Smith | jamessmith#example.com | 102 | 2019-06-14 20:17:26.759086+00 | 1 | 1995.00
2 | Mary | Johnson | maryjohnson#example.com | 103 | 2019-06-14 20:17:26.759098+00 | 2 | 200.00
3 | John | Williams | johnwilliams#example.com | 104 | 2019-06-14 20:17:26.759109+00 | 1 | 60.00
4 | Patricia | Brown | patriciabrown#example.com | | | |
5 | Michael | Garcia | michaelgarcia#example.com | 105 | 2019-06-14 20:17:26.75912+00 | 1 | 400.00
(5 rows)
You can change the LEFT JOINs to JOINS if you don't want to see customers with no purchases.

Selecting on a condition in window function postgresql

I am using postgresql and applying window function. previously I had to find first gid with same last name , and address(street_address and city) so i simply put last name in partition by clause in window function.
but now I have requirement to find first g_id of which last name is not same. while address is same How can I do it ?
This is what i was doing previously.
SELECT g_id as g_id,
First_value(g_id)
OVER (PARTITION BY lname,street_address , city ,
order by last_date DESC NULLS LAST )as c_id,
street_address as street_address FROM my table;
lets say this is my db
g_id | l_name | street_address | city | last_date
_________________________________________________
x1 | bar | abc road | khi | 11-6-19
x2 | bar | abc road | khi | 12-6-19
x3 | foo | abc road | khi | 19-6-19
x4 | harry | abc road | khi | 17-6-19
x5 | bar | xyz road | khi | 11-6-19
_________________________________________________
In previous scenario :
for if i run for the first row my c_id, it should return 'x2' as it considers these rows:
_________________________________________________
g_id | l_name | street_address | city | last_date
_________________________________________________
x1 | bar | abc road | khi | 11-6-19
x2 | bar | abc road | khi | 12-6-19
_________________________________________________
and return a row with latest last_date.
what i want now to select these rows (rows with same street_address and city but no same l_name):
g_id | l_name | street_address | city | last_date
_________________________________________________
x1 | bar | abc road | khi | 11-6-19
x3 | foo | abc road | khi | 19-6-19
x4 | harry | abc road | khi | 17-6-19
_________________________________________________
and output will be x3.
somehow i want to compare last_name column if it is not equals to the current value of last name and then partition by address field. and if no rows satisfy the condition c_id should be equal to current g_id
Looking at your expected output,it's not clear whether you want earliest or oldest for each group. You may change the ORDER BY accordingly for last_date in this query which uses DISTINCT ON
SELECT DISTINCT ON ( street_address, city, l_name) *
FROM mytable
ORDER BY street_address,
city,
l_name,
last_date --change this to last_date desc if you want latest
DEMO
After discussing the details in this chat:
demo:db<>fiddle
SELECT DISTINCT ON (t1.g_id)
t1.*,
COALESCE(t2.g_id, t1.g_id) AS g_id
FROM
mytable t1
LEFT JOIN mytable t2
ON t1.street_address = t2.street_address AND t1.l_name != t2.l_name
ORDER BY t1.g_id, t2.last_date DESC
here is how I solved it using subquery
creating example table.
CREATE TABLE mytable
("g_id" varchar(2), "l_name" varchar(5), "street_address" varchar(8), "city" varchar(3), "last_date" date)
;
INSERT INTO mytable
("g_id", "l_name", "street_address", "city", "last_date")
VALUES
('x1', 'bar', 'abc road', 'khi', '11-6-19'),
('x2', 'bar', 'abc road', 'khi', '12-6-19'),
('x3', 'foo', 'abc road', 'khi', '19-6-19'),
('x4', 'harry', 'abc road', 'khi', '17-6-19'),
('x5', 'bar', 'xyz road', 'khi', '11-6-19')
;
query to get g_ids
SELECT * ,
(select b.g_id from mytable b where (base.g_id = b.g_id) or (base.l_name <>
b.l_name and base.street_address = b.street_address and base.city = b.city )
order by b.last_date desc limit 1)
from mytable base

Select statement with join, or subquery limit

For few days now I'm trying to solve this problem.
I have table group_user, group_name.
What I wanna to do is select user groups, than description that group (from group_name), and 10 other users from the group.
It's not problem with first two. The problem is, that I'm nowhere to get limit users.
I can select user_group, and other users in that group. I don't know how to limit that.
Using:
SELECT a.g_id,b.group,b.userid
FROM group_user AS a
RIGHT JOIN
(SELECT g_id as group, u_id as userid FROM group_user) AS b ON a.g_id=b.group
WHERE u_id=112
It showing me, my user groups and users in that group. But when I'm trying to limit in subwuery, it limits all, not particular group.
I tried, Select users, with using IN where was goups of my user without luck.
I was thinking maybe group and having will help, but I can't see how I could use it.
So my question is, how can I limit subquery result in MySQL where the subquery is built on result of query.
I think im overload and maybe I don't see something.
UPDATE to show what I really wanna accomplish here's another piece of code.
SELECT g_id FROM group_user WHERE user_id = 112
So I get all groups that user is in let, saye each of that select is var extra_group, so second query will be
SELECT u_id FROM group_user WHERE group_id = extra_group LIMIT 10
I need to do same as above, in one query.
another UPDATE after MIKE post.
I should ADD that, user can be in more than 1 group. So I think the real problem is, that I don't have any clue how to select those groups and in same query select 10 users for selected groups, so in result could be
g_id u_id
1 | 2
1 | 3
1 | 4
3 | 3
3 | 8
where g_id is user groups from that query
SELECT g_id FROM group_user WHERE user_id = 112
Create sample tables and add data:
CREATE TABLE `group_user` (
`u_id` int(11) DEFAULT NULL,
`g_id` int(11) DEFAULT NULL,
`apply_date` date DEFAULT NULL
);
CREATE TABLE `group_name` (
`g_id` int(11) DEFAULT NULL,
`g_name` varchar(255) DEFAULT NULL
);
INSERT INTO `group_name` VALUES
(1, 'Group 1'), (2, 'Group 2'), (3, 'Group 3'), (4, 'Group 4'), (5, 'Group 5');
INSERT INTO `group_user` VALUES
(1, 1, '2010-12-01'), (1, 2, '2010-12-01'), (1, 3, '2010-12-01'), (1, 4, '2010-12-01'), (1, 5, '2010-12-01'),
(2, 1, '2010-12-02'), (2, 2, '2010-12-02'),
(3, 1, '2010-12-03'), (3, 2, '2010-12-03'), (3, 3, '2010-12-03'), (3, 4, '2010-12-03'),
(4, 1, '2010-12-04'), (4, 2, '2010-12-04'),
(5, 1, '2010-12-05'), (5, 2, '2010-12-05'),
(6, 1, '2010-12-06'), (6, 2, '2010-12-06'),
(7, 1, '2010-12-07'), (7, 2, '2010-12-07'), (7, 3, '2010-12-07'), (7, 4, '2010-12-07'), (7, 5, '2010-12-07'),
(8, 1, '2010-12-08'), (8, 2, '2010-12-08'),
(9, 1, '2010-12-09'), (9, 2, '2010-12-09'), (9, 3, '2010-12-09'), (9, 4, '2010-12-09'), (9, 5, '2010-12-09');
Select the groups of which user u_id == 1 is a member. Then for each group select a maximum of 4 members (excluding user u_id == 1), ordered by descending apply_date:
SELECT u3.g_id, g.g_name, u3.u_id, u3.apply_date
FROM (
SELECT
u1.g_id,
u1.u_id,
u1.apply_date,
IF( #prev_gid <> u1.g_id, #user_index := 1, #user_index := #user_index + 1 ) AS user_index,
#prev_gid := u1.g_id AS prev_gid
FROM group_user AS u1
JOIN (SELECT #prev_gid := 0, #user_index := NULL) AS vars
JOIN group_user AS u2
ON u2.g_id = u1.g_id
AND u2.u_id = 1
AND u1.u_id <> 1
ORDER BY u1.g_id, u1.apply_date DESC, u1.u_id
) AS u3
JOIN group_name AS g ON g.g_id = u3.g_id
WHERE u3.user_index <= 4
ORDER BY u3.g_id, u3.apply_date DESC, u3.u_id;
+------+---------+------+------------+
| g_id | g_name | u_id | apply_date |
+------+---------+------+------------+
| 1 | Group 1 | 5 | 2010-12-05 |
| 1 | Group 1 | 4 | 2010-12-04 |
| 1 | Group 1 | 3 | 2010-12-03 |
| 1 | Group 1 | 2 | 2010-12-02 |
| 2 | Group 2 | 5 | 2010-12-05 |
| 2 | Group 2 | 4 | 2010-12-04 |
| 2 | Group 2 | 3 | 2010-12-03 |
| 2 | Group 2 | 2 | 2010-12-02 |
| 3 | Group 3 | 9 | 2010-12-09 |
| 3 | Group 3 | 7 | 2010-12-07 |
| 3 | Group 3 | 3 | 2010-12-03 |
| 4 | Group 4 | 9 | 2010-12-09 |
| 4 | Group 4 | 7 | 2010-12-07 |
| 4 | Group 4 | 3 | 2010-12-03 |
| 5 | Group 5 | 9 | 2010-12-09 |
| 5 | Group 5 | 7 | 2010-12-07 |
+------+---------+------+------------+