Postgresql - How to use string_to_array on another column value - postgresql

How can I use string_to_array or split_part on another column value.
I want do something like select * from tenants where id IN (select string_to_array(select ancestry from tenants where id = 39,'/'));
-[ RECORD 1 ]-------------+----------------------
id | 1
domain |
subdomain |
name | My Company
login_text |
logo_file_name |
logo_content_type |
logo_file_size |
logo_updated_at |
login_logo_file_name |
login_logo_content_type |
login_logo_file_size |
login_logo_updated_at |
ancestry |
divisible | t
description | Tenant for My Company
use_config_for_attributes | t
default_miq_group_id | 1
source_type |
source_id |
-[ RECORD 3 ]-------------+----------------------
id | 35
domain |
subdomain |
name | Tenant_2
login_text |
logo_file_name |
logo_content_type |
logo_file_size |
logo_updated_at |
login_logo_file_name |
login_logo_content_type |
login_logo_file_size |
login_logo_updated_at |
ancestry | 1
divisible | t
description | Tenant_2
use_config_for_attributes | f
default_miq_group_id | 36
source_type |
source_id |
-[ RECORD 7 ]-------------+----------------------
id | 39
domain |
subdomain |
name | Child_Teanant_202
login_text |
logo_file_name |
logo_content_type |
logo_file_size |
logo_updated_at |
login_logo_file_name |
login_logo_content_type |
login_logo_file_size |
login_logo_updated_at |
ancestry | 1/35
divisible | t
description | Child_Teanant_202
use_config_for_attributes | f
default_miq_group_id | 52
source_type |
source_id |

Use regex to enforce word boundaries:
select *
from tenants
where (select ancestry from tenants where id = 39)
~ ('\y' || id || '\y')
See live demo.
Without the word boundaries an id of 1 would match an ancestry of 123.
Note Postgres's unusual regex for word boundary \y, which elsewhere is \b.

There are two ways to solve this.
One is to simply unnest the elements of ancestry
select *
from tenants
where id in (select a.id::int
from tenants t2
cross join unnest(string_to_array(t2.ancestry, '/')) as a(id)
where t2.id = 39);
Converting the string to an array in order to be able to use the = ANY() operator is a bit tricky, because you need two levels of parentheses plus a type cast to an integer array to make that work:
select *
from tenants
where id = any ((select string_to_array(t2.ancestry, '/')
from tenants t2
where t2.id = 39)::int[]);
Online example

Related

2 Level pivot using Postgresql

I have a table whose schema along with data (table_name : raw_data) appears to be this :
name | category | clear_date |
A | GOOD | 2020-05-30 |
A | GOOD | 2020-05-30 |
A | GOOD | 2020-05-30 |
A | GOOD | 2020-05-30 |
A | BAD | 2020-05-30 |
A | BAD | 2020-05-30 |
Now if I perform a "groupby" operation using the following statement :
SELECT name, category, date(clear_date), count(clear_date)
FROM raw_data
GROUP BY name, category, date(clear_date)
ORDER BY name
I get the following answer :
name | caetgory | date | count |
A | GOOD |2020-05-30 | 4 |
A | BAD |2020-05-30 | 1 |
A | BAD |2020-05-31 | 1 |
IN order to produce the pivot in following format :
name | category | 2020-05-30 | 2020-05-31 |
A | GOOD | 4 | NULL |
A | BAD | 1 | 1 |
I am using the following query :
select * from crosstab (
'select name, category, date(clear_date), count(clear_date) from raw_data group by name, category, date(clear_date) order by 1,2,3',
'select distinct date(clear_date) from raw_data order by 1'
)
as newtable (
node_name varchar, alarm_name varchar, "2020-05-30" integer, "2020-05-31" integer
)
ORDER BY name
But I am getting results as follows :
name | category | 2020-05-30 | 2020-05-31 |
A | BAD | 4 | 1 |
Can anyone please try to suggest how can i achieve the result mentioned above. It appears crosstab removes the duplicate entry of A automatically.
Not sure if this is possible using crosstab because you have a missing records in some dates. Here is an example how to get expected result but not sure is what you need. Anyway hope this helps.
SELECT r1.*, r2.counter AS "2020-05-30", r3.counter AS "2020-05-31"
FROM (
SELECT DISTINCT name, category
FROM raw_data
) AS r1
LEFT JOIN (
SELECT name, category, count(*) AS counter
FROM raw_data
WHERE clear_date = '2020-05-30'
GROUP BY name, category
) AS r2 ON (r2.category = r1.category AND r2.name = r1.name)
LEFT JOIN (
SELECT name, category, count(*) AS counter
FROM raw_data
WHERE clear_date = '2020-05-31'
GROUP BY name, category
) AS r3 ON (r3.category = r1.category AND r3.name = r1.name)
ORDER BY r1.category DESC;

SUM values from two tables with GROUP BY and WHERE

I have two tables below named sent_table and received_table. I am attempting to mash them together in a query to achieve output_table. All my attempts so far result in a huge amount of duplicates and totally bogus sum values.
I am assuming I would need to use GROUP BY and WHERE to achieve this goal. I want to be able to filter based on the users name.
sent_table
+----+------+-------+----------+
| id | name | value | order_id |
+----+------+-------+----------+
| 1 | dave | 100 | 1 |
| 2 | dave | 200 | 1 |
| 3 | dave | 300 | 2 |
+----+------+-------+----------+
received_table
+----+------+-------+----------+
| id | name | value | order_id |
+----+------+-------+----------+
| 1 | dave | 400 | 1 |
| 2 | dave | 500 | 2 |
| 3 | dave | 600 | 2 |
+----+------+-------+----------+
output table
+------+----------+----------+
| sent | received | order_id |
+------+----------+----------+
| 300 | 400 | 1 |
| 300 | 1100 | 2 |
+------+----------+----------+
I tried the following with no joy. This does not impose any restrictions on how I would desire to solve this problem. It is just how I attempted to do it.
SELECT *
FROM
( select SUM(value) as sent, order_id FROM sent_table WHERE name='dave' GROUP BY order_id) A
CROSS JOIN
( select SUM(value) as received, order_id FROM received_table WHERE name='dave' GROUP BY order_id) B
Any help would be greatly appreciated.
Do the sums on each table, grouping by order_id, then join the results. To get the rows even if one side is missing, do a FULL OUTER JOIN:
SELECT COALESCE(s.order_id, r.order_id) AS order_id, s.sent, r.received
FROM (
SELECT order_id, SUM(value) AS sent
FROM sent
GROUP BY order_id
) s
FULL OUTER JOIN (
SELECT order_id, SUM(value) AS received
FROM received
GROUP BY order_id
) r
USING (order_id)
ORDER BY 1
Result:
| order_id | sent | received |
| -------- | ---- | -------- |
| 1 | 300 | 400 |
| 2 | | 1100 |
Note the COALESCE on the order_id, so that if it's missing from sent it will be taken from recevied, so that that value will never be NULL.
If you want to have 0 in place of NULL (when e.g. there is no record for that order_id in either sent or received), you would do COALESCE(s.sent, 0) AS sent, COALESCE(r.received, 0) AS received.
https://www.db-fiddle.com/f/nq3xYrcys16eUrBRHT6xLL/2

postgres select latest record per entity except on field value

I have a log table that looks something like this:
---------------------------------------------
| id | company | type | date_created |notes|
---------------------------------------------
| 1 | co1 | | 2016-06-30 | ... |
| 2 | co2 | ERROR | 2016-06-30 | ... |
| 3 | co1 | | 2016-06-29 | ... |
| 4 | co2 | | 2016-06-29 | ... |
I have the following which selects the latest record per entity:
SELECT *
FROM import_data_log a
JOIN (SELECT company, max(date_created) date_created
FROM import_data_log
GROUP BY company) b
ON a.company = b.company AND a.date_created = b.date_created
which gives the result:
| 1 | co1 | | 2016-06-30 | ... |
| 2 | co2 | ERROR | 2016-06-30 | ... |
I need to add a condition that does not select the entry with type = ERROR and get the next highest date for that company, so it would give:
| 1 | co1 | | 2016-06-30 | ... |
| 4 | co2 | | 2016-06-29 | ... |
Any ideas? It's probably something simple but for the life of me I can't seem to get it to work.
UPDATE / FIX:
Ok so after a lot of hair pulling, for anyone running into this issue, apparently Postgres doesn't compare null fields with anything, so it completely ignores all rows with type = null.
The way I fixed it is this, there is probably a nicer solution out there but for now this works:
SELECT *
FROM import_data_log a
JOIN (SELECT company, max(date_created) date_created
FROM import_data_log
WHERE (type <> 'ERROR' OR type is null)
GROUP BY company) b
ON a.company = b.company AND a.date_created = b.date_created
Use the below Query
SELECT id,company,type,max(date_created),notes
FROM import_data_log
WHERE type != 'ERROR'
GROUP BY company
select distinct on (company) *
from import_data_log
where type is distinct from 'ERROR'
order by company, date_created desc
Check distinct on and is [not] distinct from:
Ordinary comparison operators yield null (signifying "unknown"), not true or false, when either input is null. For example, 7 = NULL yields null, as does 7 <> NULL. When this behavior is not suitable, use the IS [ NOT ] DISTINCT FROM constructs

Left outer join - how to return a boolean for existence in the second table?

In PostgreSQL 9 on CentOS 6 there are 60000 records in pref_users table:
# \d pref_users
Table "public.pref_users"
Column | Type | Modifiers
------------+-----------------------------+--------------------
id | character varying(32) | not null
first_name | character varying(64) | not null
last_name | character varying(64) |
login | timestamp without time zone | default now()
last_ip | inet |
(... more columns skipped...)
And another table holds around 500 ids of users which are not allowed to play anymore:
# \d pref_ban2
Table "public.pref_ban2"
Column | Type | Modifiers
------------+-----------------------------+---------------
id | character varying(32) | not null
first_name | character varying(64) |
last_name | character varying(64) |
city | character varying(64) |
last_ip | inet |
reason | character varying(128) |
created | timestamp without time zone | default now()
Indexes:
"pref_ban2_pkey" PRIMARY KEY, btree (id)
In a PHP script I am trying to display all 60000 users from pref_users in a jQuery-dataTable. And I would like to mark the banned users (the users found in pref_ban2).
Which means I need a column named ban for each record in my query holding true or false.
So I am trying a left outer join query:
# select
b.id, -- how to make this column a boolean?
u.id,
u.first_name,
u.last_name,
u.city,
u.last_ip,
to_char(u.login, 'DD.MM.YYYY') as day
from pref_users u left outer join pref_ban2 b on u.id=b.id
limit 10;
id | id | first_name | last_name | city | last_ip | day
----+----------+-------------+-----------+-------------+-----------------+------------
| DE1 | Alex | | Bochum | 2.206.0.224 | 21.11.2014
| DE100032 | Княжна Мэри | | London | 151.50.61.131 | 01.02.2014
| DE10011 | Aлександр Ш | | Симферополь | 37.57.108.13 | 01.01.2014
| DE10016 | Semen10 | | usa | 69.123.171.15 | 25.06.2014
| DE10018 | Горловка | | Горловка | 178.216.97.214 | 25.09.2011
| DE10019 | -Дмитрий- | | пермь | 5.140.81.95 | 21.11.2014
| DE10047 | Василий | | Cумы | 95.132.42.185 | 25.07.2014
| DE10054 | Maedhros | | Чикаго | 207.246.176.110 | 26.06.2014
| DE10062 | ssergw | | москва | 46.188.125.206 | 12.09.2014
| DE10086 | Вадим | | Тула | 109.111.26.176 | 26.02.2012
(10 rows)
As you can see the b.id column above is empty - because these 10 users aren't banned.
How to get a false value in that column instead of a String?
And I am not after some coalesceor case expression, but am looking for "the proper" way to do such a query.
"IS NULL" and "IS NOT NULL" return a boolean, so this should make it easy.
I think this is all you need?
SELECT
b.id IS NOT NULL as is_banned, -- The value of "is_banned" will be a boolean
Not sure if you need the "NOT" or not, but you'll get a bool either way.
A CASE or COALESCE statement with an outer join IS the proper way to do this.
select
CASE
WHEN b.id IS NULL THEN true
ELSE false
END AS banned,
u.id,
u.first_name,
u.last_name,
u.city,
u.last_ip,
to_char(u.login, 'DD.MM.YYYY') as day
from pref_users u
left outer join pref_ban2 b
on u.id=b.id
limit 10;

Recursive SQL PostgreSQL Empty Result Set

The categories table:
=# \d
List of relations
Schema | Name | Type | Owner
--------+-------------+-------+-------
public | categories | table | pgsql
public | products | table | pgsql
public | ticketlines | table | pgsql
(3 rows)
Contents of categories:
=# select * from categories;
id | name | parentid
----+--------+----------
1 | Rack |
2 | Women | 1
3 | Shorts | 2
4 | Wares |
5 | Toys | 4
6 | Trucks | 5
(6 rows)
Running the following query:
WITH RECURSIVE nodes_cte(name, id, parentid, depth, path) AS (
-- Base case?
SELECT c.name,
c.id,
c.parentid,
1::INT AS depth,
c.id::TEXT AS path
FROM categories c
WHERE c.parentid = ''
UNION ALL
-- nth case
SELECT c.name,
c.id,
c.parentid,
n.depth + 1 AS depth,
(n.path || '->' || c.id::TEXT)
FROM nodes_cte n
JOIN categories c on n.id = c.parentid
)
SELECT * FROM nodes_cte AS n GROUP BY n.name, n.id, n.parentid, n.depth, n.path ORDER BY n.id ASC
;
yields these results:
name | id | parentid | depth | path
--------+----+----------+-------+---------
Rack | 1 | | 1 | 1
Women | 2 | 1 | 2 | 1->2
Shorts | 3 | 2 | 3 | 1->2->3
Wares | 4 | | 1 | 4
Toys | 5 | 4 | 2 | 4->5
Trucks | 6 | 5 | 3 | 4->5->6
(6 rows)
Great!
But given a similar table (categories):
=# \d categories
Table "public.categories"
Column | Type | Modifiers
----------+-------------------+-----------
id | character varying | not null
name | character varying | not null
parentid | character varying |
image | bytea |
Indexes:
"categories_pkey" PRIMARY KEY, btree (id)
"categories_name_inx" UNIQUE, btree (name)
Referenced by:
TABLE "products" CONSTRAINT "products_fk_1" FOREIGN KEY (category) REFERENCES categories(id)
=# select * from categories;
id | name | parentid | image
--------------------------------------+-------+--------------------------------------+-------
611572c9-326d-4cf9-ae4a-af5269fc788e | Rack | |
22d15300-40b5-4f43-a8d1-902b8d4c5409 | Women | 611572c9-326d-4cf9-ae4a-af5269fc788e |
6b061073-96f4-49a1-9205-bab7c878f0cf | Wares | |
3f018dfb-e6ee-40d1-9dbc-31e6201e7625 | Toys | 6b061073-96f4-49a1-9205-bab7c878f0cf |
(4 rows)
the same query produces zero rows.
Why?
Is it something to do with primary / foreign keys?
WHERE COALESCE(parent_id, '') = ''
Worked. Thank you.