How to set values from recursive query in PostgreSQL? - postgresql

I have a query which gives a result:
id | manager_id | level | star_level
----+------------+-------+------------
1 | NULL | 1 | 0
2 | 1 | 2 | 1
3 | 2 | 3 | 1
4 | 3 | 4 | 2
5 | 4 | 5 | 2
6 | 5 | 6 | 2
7 | 6 | 7 | 3
8 | 7 | 8 | 3
9 | 8 | 9 | 4
(9 rows)
Here is the query:
WITH RECURSIVE parents AS (
SELECT e.id
, e.manager_id
, 1 AS level
, CAST(s.is_star AS INTEGER) AS star_level
FROM employees AS e
INNER JOIN skills AS s
ON e.skill_id = s.id
WHERE manager_id IS NULL
UNION ALL
SELECT e.id
, e.manager_id
, p.level + 1 AS level
, p.star_level + CAST(s.is_star AS INTEGER) AS star_level
FROM employees AS e
INNER JOIN skills AS s
ON e.skill_id = s.id
INNER JOIN parents AS p
ON e.manager_id = p.id
WHERE e.manager_id = p.id
)
SELECT *
FROM parents
;
Can you please tell me how you can change the query so that in the same query the level and star_level values ​​can be written to the corresponding columns?
Demo data:
create table Employees(
id INT,
name VARCHAR,
manager_id INT,
skill_id INT,
level INT,
star_level INT
);
create table Skills(
id INT,
name VARCHAR,
is_star BOOL
);
INSERT INTO Employees
(id, name, manager_id, skill_id)
VALUES
(1, 'Employee 1', NULL, 1),
(2, 'Employee 2', 1, 2),
(3, 'Employee 3', 2, 3),
(4, 'Employee 4', 3, 4),
(5, 'Employee 5', 4, 5),
(6, 'Employee 6', 5, 1),
(7, 'Employee 7', 6, 2),
(8, 'Employee 8', 7, 3),
(9, 'Employee 9', 8, 4)
;
INSERT INTO Skills
(id, name, is_star)
VALUES
(1, 'Skill 1', FALSE),
(2, 'Skill 2', TRUE),
(3, 'Skill 3', FALSE),
(4, 'Skill 4', TRUE),
(5, 'Skill 5', FALSE)
;
As a result, I need a query which will count level and star_level columns for Employees table and write their values (in Employees table) in one query.

You can use an UPDATE statement together with your CTE:
with recursive parents as (
... your original query goes here ...
)
update employees
set level = p.level,
star_level = p.star_level
from parents p
where employees.id = p.id;

Related

Postgres Query to Generate a Table Matrix

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;

Cannot UPDATE a CTE ( but SELECT works)?

According to the docs and previous answers on this forum, it should be trivial to UPDATE a CTE in a WITH query ... but I cannot succeed !
What am I doing wrong for example here in this simple example ?
WITH val AS (VALUES (1, 11), (2, 22), (3, 33))
UPDATE val SET column1=column1*2 ;
this throws me an error
ERROR: relation "val" does not exist
LINE 3: UPDATE val SET column1=column1*2 ;
^
whereas this example with SELECT works:
WITH val AS (VALUES (1, 11), (2, 22), (3, 33))
SELECT 2*column1 as new, column2 FROM val;
new | column2
-----+---------
2 | 11
4 | 22
6 | 33
(3 rows)

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.

TSQL - Merge Group with Range?

Example code:
Declare #table1 TABLE(myIndex int identity(1,1),[cal] int, Name Nvarchar(20) );
Declare #range int = 5;
INSERT INTO #table1 ([cal], Name)
VALUES (1, 'A'), (3, 'B'), (4, 'C'), (2, 'D'), (3, 'E'), (4, 'F'), (6, 'G'), (2, 'H');
SELECT * FROM #table1
Output:
myIndex | Sum(cal) | Name |
--------+----------+------+
1 | 1 | A |
2 | 3 | B |
3 | 4 | C |
4 | 2 | D |
5 | 3 | E |
6 | 4 | F |
7 | 6 | G |
8 | 2 | H |
I wan to Sum(cal) > 5 then join string
TSQL - 2012 - Report Expect Example
myIndex | Sum(cal) | Name | Description
--------+----------+--------+--------------------------------
1 | 7 | A,B,C | (Explain: First Sum(cal) > 5, Merge String)
2 | 9 | D,E,F | (Explain:Second Sum(cal) > 5, Merge String)
3 | 6 | G | (Explain:Third, Sum(cal) > 5, Merge String)
4 | 2 | H | (Explain:Last, still one last step)
Please, help me to resolve the problems.
This is a solution using a cursor. Hope this could help. Try it and see if performance could be acceptable.
Declare #table1 TABLE(myIndex int identity(1,1),[cal] int, Name Nvarchar(20) );
Declare #range int = 5;
INSERT INTO #table1 ([cal], Name)
VALUES (1, 'A'), (3, 'B'), (4, 'C'), (2, 'D'), (3, 'E'), (4, 'F'), (6, 'G'), (2, 'H');
SELECT * FROM #table1
-----
DECLARE #aggregates TABLE (myIndex int identity(1,1),SumCal int, AggregateNames Nvarchar(MAX) );
DECLARE #SumCal INT
, #AggregateNames NVARCHAR(MAX)
, #cal INT
, #Name Nvarchar(20)
;
SET #SumCal = 0;
SET #AggregateNames = NULL;
DECLARE cur CURSOR LOCAL FAST_FORWARD FOR
SELECT [cal], name from #table1 ORDER BY myIndex
OPEN cur
FETCH NEXT FROM cur INTO #cal, #Name
WHILE ##FETCH_STATUS = 0
BEGIN
SET #SumCal = #SumCal + #cal
SET #AggregateNames = ISNULL(#AggregateNames + ',', '') + #Name
IF #SumCal > 5
BEGIN
INSERT INTO #aggregates([SumCal], AggregateNames)
VALUES(#SumCal, #AggregateNames)
SET #SumCal = 0
SET #AggregateNames = NULL
END
FETCH NEXT FROM cur INTO #cal, #Name
END
IF #SumCal > 0
BEGIN
INSERT INTO #aggregates([SumCal], AggregateNames)
VALUES(#SumCal, #AggregateNames)
END
CLOSE cur
DEALLOCATE cur
SELECT * FROM #aggregates

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 |
+------+---------+------+------------+