How would you go about returning the key_results, based on the key_id's listed in this comma delimited key_list?
SELECT key_list FROM some.place where key_number=1234;
key_list
----------------
{32,35,58,63,89}
SELECT key_id, key_result FROM some.otherplace;
key_id | key_result
--------------------------
32 | frisbee
33 | duckhunt
34 | hairplugs
35 | sparkplugs
Because your string is a valid PostgreSQL array literal, you can use it as input to = ANY with a cast to integer[]:
SELECT o.key_id, o.key_result
FROM some.otherplace o
INNER JOIN some.place p ON (o.key_id = ANY (key_list::integer[]))
Really though ... this is bad schema design. For why, see this post. At minimum you should store an actual array.
Other useful tools when dealing with comma-separated values are string_to_array, unnest, and regexp_split_to_table.
Related
I saw examples using XMLTABLE to sort comma-separated strings in Oracle. While we should never have to do this with a proper database design, it made me curious and there is one thing I don't understand at all:
Why am I allowed to cross join XMLTABLE referencing columns from the other table? I would expect to have to apply a lateral join (CROSS APPLY), but this doesn't seem needed. This query works:
select *
from
(
select 'b,a,d' as csv from dual
union all
select 'w,o,r,s,e' as csv from dual
) t
cross join xmltable
(
'if (contains($csv, ",")) then string-join(for $str in ora:tokenize($csv, ",") order by $str return $str, ",") else $csv'
passing t.csv as "csv"
columns sorted varchar2(4000) path '.'
) x
Result:
+-----------+-----------+
| CSV | SORTED |
+-----------+-----------+
| b,a,d | a,b,d |
| w,o,r,s,e | e,o,r,s,w |
+-----------+-----------+
I am passing t.csv which should not be accessible on the right side of the cross join in my opinion.
Can anybody explain what is happening here? I have come to think that Oracle muddles its way through here for some reason, thus violating the SQL standard. Am I right?
If you can explain what Oracle does here, this will certainly also explain why adding this
where sorted <> 'x'
leads to unexpected results. Unexpected to me that is :-)
Result with WHERE clause:
+-----------+--------+
| CSV | SORTED |
+-----------+--------+
| b,a,d | a,b,d |
| w,o,r,s,e | a,b,d |
+-----------+--------+
Demo: https://dbfiddle.uk/?rdbms=oracle_18&fiddle=a9497bec423a3facbd29b49b3a40a350
Your "why" question might be difficult to answer; it is likely because that is how Oracle performed correlated queries using its legacy comma-join syntax and then adding ANSI syntax later and then in Oracle 12c added CROSS APPLY but finding documentation to back this up will be challenging.
However, if you can force the evaluation of the correlated CROSS JOIN (by performing a useless row specific operation, like generating the ROWNUM pseudo-column) before applying the WHERE filter clause then you can get the query to work:
WITH t ( csv ) AS (
select 'b,a,d' from dual union all
select 'w,o,r,s,e' from dual union all
select 'w,o,r,s,e,r' from dual
)
SELECT csv,
sorted
FROM (
select ROWNUM as id,
t.csv,
s.sorted
from t
CROSS JOIN xmltable (
'if (contains($csv, ",")) then string-join(for $str in ora:tokenize($csv, ",") order by $str return $str, ",") else $csv'
passing t.csv as "csv"
columns sorted varchar2(4000) path '.'
) s
)
WHERE sorted <> 'x';
Outputs:
CSV | SORTED
:---------- | :----------
b,a,d | a,b,d
w,o,r,s,e | e,o,r,s,w
w,o,r,s,e,r | e,o,r,r,s,w
db<>fiddle here
I have the following SQL query:
SELECT TOP 3 accounts.username
,COUNT(accounts.username) AS count
FROM relationships
JOIN accounts ON relationships.account = accounts.id
WHERE relationships.following = 4
AND relationships.account IN (
SELECT relationships.following
FROM relationships
WHERE relationships.account = 8
);
I want to return the total count of accounts.username and the first 3 accounts.username (in no particular order). Unfortunately accounts.username and COUNT(accounts.username) cannot coexist. The query works fine removing one of the them. I don't want to send the request twice with different select bodies. The count column could span to 1000+ so I would prefer to calculate it in SQL rather in code.
The current query returns the error Column 'accounts.username' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause. which has not led me anywhere and this is different to other questions as I do not want to use the 'group by' clause. Is there a way to do this with FOR JSON AUTO?
The desired output could be:
+-------+----------+
| count | username |
+-------+----------+
| 1551 | simon1 |
| 1551 | simon2 |
| 1551 | simon3 |
+-------+----------+
or
+----------------------------------------------------------------+
| JSON_F52E2B61-18A1-11d1-B105-00805F49916B |
+----------------------------------------------------------------+
| [{"count": 1551, "usernames": ["simon1", "simon2", "simon3"]}] |
+----------------------------------------------------------------+
If you want to display the total count of rows that satisfy the filter conditions (and where username is not null) in an additional column in your resultset, then you could use window functions:
SELECT TOP 3
a.username,
COUNT(a.username) OVER() AS cnt
FROM relationships r
JOIN accounts a ON r.account = a.id
WHERE
r.following = 4
AND EXISTS (
SELECT 1 FROM relationships t1 WHERE r1.account = 8 AND r1.following = r.account
)
;
Side notes:
if username is not nullable, use COUNT(*) rather than COUNT(a.username): this is more efficient since it does not require the database to check every value for nullity
table aliases make the query easier to write, read and maintain
I usually prefer EXISTS over IN (but here this is mostly a matter of taste, as both techniques should work fine for your use case)
I searched, but seems I haven't found the right keyword for describing what I am trying to achieve, so please be lenient if this is a known problem, just point me to the right keywords.
I have following tables/entries:
select * from personnes where id=66;
id | aclid | referendid | login | validated | passwd
66 | | | toto#tiiti.com | f | $2y$10$w3DRh/g2Tebu/mkMcQz32OUB.dDjFiBP99vWlMrrPWpR45JZDdw4W
and
select * from pattributs where (name='nom' OR name='prenom') AND persid=66;
id | name | value | persid
----+--------+-------+--------
90 | prenom | Jean | 66
91 | nom | Meyer | 66
Now I use that form for not cluttering the main table, since depending on the case, I record the name, or not....
but having a view as a table of the completed table would be nice, so I tried:
select (personnes."id","login",
(select "value" from pattributs where "name"='nom' AND "persid"=66),
(select "value" from pattributs where "name"='prenom' AND "persid"=66)
) from personnes where personnes.id=66;
which seems to do the job:
row
--------------------------------
(66,toto#tiiti.com,Meyer,Jean)
but the column tags disappeared, and being able to fetch them from the invoking php script is immensely useful, but when I add:
select (personnes."id","login",
(select "value" from pattributs where "name"='nom' AND "persid"=66),
(select "value" from pattributs where "name"='prenom' AND "persid"=66) as 'prenom')
from personnes where personnes.id=66;
I get a syntax error at the as directive... So probably I haven't understood how to do this properly, the braces indicate that this isn't anymore in tabular form), so how can I achieve the following result:
id | login | nom | prenom
66 |toto#tiiti.com | Meyer | Jean
The idea being to store a suitable view for each use case, bundling only the relevant columns.
Thanks in advance
To answer your question: You are loosing the column names because you are creating a simple data set, a row. This is done by your braces. Without them you should get your expected result.
But your solution is not very well: You should avoid to calculate your single columns in every single subquery. This could be done easily in one SELECT:
select
pe.id,
pe.login,
MIN(pa.value) FILTER (WHERE pa.name = 'nom') as nom,
MIN(pa.value) FILTER (WHERE pa.name = 'prenom') as prenom
from
personnes pe
join pattributs pa ON pe.id = pa.persid AND pe.id = 66
where pa.name = 'nom' or pa.name = 'prenom'
group by pe.id, pe.login
First you'll need a JOIN to get the right datasets of both tables together. You should join on the id.
Then you have the problem that you have two rows for the name (which seems not very well designed, why not two columns?). These two values can be grouped by the id. Now you could aggregate them.
What I am doing is to "aggregate" them (it doesn't matter what function I am using because it should be only one value). The FILTER clause filters out the right value.
I am working on a query that will fetch multipart messages from 2 tables. However, it only works IF there are multiple parts. If there is only a one part message then the the join condition won't be true anymore. How could I make it to work for both single and multipart messages?
Right now it fails if there is an entry in outbox and nothing in outbox_multipart.
My first table is "outbox" that looks like this.
TextDecoded | ID | CreatorID
Helllo, m.. | 123 | Martin
Yes, I wi.. | 124 | Martin
My second table is "outbox_multipart" that looks very similar.
TextDecoded | ID | SequencePosition
my name i.. | 123 | 2
s Martin. | 123 | 3
ll do tha.. | 124 | 2
t tomorrow. | 124 | 3
My query so far
SELECT
CONCAT(ob.TextDecoded,
GROUP_CONCAT(obm.TextDecoded
ORDER BY obm.SequencePosition ASC
SEPARATOR ''
)
) AS TextDecoded,
ob.ID,
ob.creatorID
FROM outbox AS ob
JOIN outbox_multipart AS obm ON obm.ID = ob.ID
GROUP BY
ob.ID,
ob.creatorID
Use a left join instead of an (implicit) inner join. Then, also use COALESCE on the TextDecoded alias to make sure that empty string (and not NULL) appears in the expected output.
SELECT
CONCAT(ob.TextDecoded,
COALESCE(GROUP_CONCAT(obm.TextDecoded
ORDER BY obm.SequencePosition
SEPARATOR ''), '')) AS TextDecoded,
ob.ID,
ob.creatorID
FROM outbox AS ob
LEFT JOIN outbox_multipart AS obm
ON obm.ID = ob.ID
GROUP BY
ob.ID,
ob.creatorID,
ob.TextDecoded;
Note: Strictly speaking, outbox.TextDecoded should also appear in the GROUP BY clause, since it is not an aggregate. I have made this change in the query.
My client wants the possibility to match a set of data against an array of regular expressions, meaning:
table:
name | officeId (foreignkey)
--------
bob | 1
alice | 1
alicia | 2
walter | 2
and he wants to do something along those lines:
get me all records of offices (officeId) where there is a member with
ANY name ~ ANY[.*ob, ali.*]
meaning
ANY of[alicia, walter] ~ ANY of [.*ob, ali.*] results in true
I could not figure it out by myself sadly :/.
Edit
The real Problem was missing form the original description:
I cannot use select disctinct officeId .. where name ~ ANY[.*ob, ali.*], because:
This application, stored data in postgres-xml columns, which means i do in fact have (after evaluating xpath('/data/clients/name/text()'))::text[]):
table:
name | officeId (foreignkey)
-----------------------------------------
[bob, alice] | 1
[anthony, walter] | 2
[alicia, walter] | 3
There is the Problem. And "you don't do that, that is horrible, why would you do it like this, store it like it is meant to be stored in a relation database, user a no-sql database for Document-based storage, use json" are no options.
I am stuck with this datamodel.
This looks pretty horrific, but the only way I can think of doing such a thing would be a hybrid of a cross-join and a semi join. On small data sets this would probably work pretty well. On large datasets, I imagine the cross-join component could hit you pretty hard.
Check it out and let me know if it works against your real data:
with patterns as (
select unnest(array['.*ob', 'ali.*']) as pattern
)
select
o.name, o.officeid
from
office o
where exists (
select null
from patterns p
where o.name ~ p.pattern
)
The semi-join helps protect you from cases where you have a name like "alicia nob" that would meet multiple search patterns would otherwise come back for every match.
You could cast the array to text.
SELECT * FROM workers WHERE (xpath('/data/clients/name/text()', xml_field))::text ~ ANY(ARRAY['wal','ant']);
When casting a string array into text, strings containing special characters or consisting of keywords are enclosed in double quotes kind of like {jimmy,"walter, james"} being two entries. Also when matching with ~ it is matched against any part of the string, not the same as LIKE where it's matched against the whole string.
Here is what I did in my test database:
test=# select id, (xpath('/data/clients/name/text()', name))::text[] as xss, officeid from workers WHERE (xpath('/data/clients/name/text()', name))::text ~ ANY(ARRAY['wal','ant']);
id | xss | officeid
----+-------------------------+----------
2 | {anthony,walter} | 2
3 | {alicia,walter} | 3
4 | {"walter, james"} | 5
5 | {jimmy,"walter, james"} | 4
(4 rows)