Search with LIKE in PostgreSQL array - arrays

I have this table:
id | name | tags
----+----------+-------------------------
1 | test.jpg | {sometags,other_things}
I need to get rows that contain specific tags by searching in array with regular expression or LIKE, like this:
SELECT * FROM images WHERE 'some%' LIKE any(tags);
But this query returns nothing.

with images (id, name, tags) as (values
(1, 'test.jpg', '{sometags, other_things}'::text[]),
(2, 'test2.jpg', '{othertags, other_things}'::text[])
)
select *
from images
where (
select bool_or(tag like 'some%')
from unnest(tags) t (tag)
);
id | name | tags
----+----------+-------------------------
1 | test.jpg | {sometags,other_things}
unnest returns a set which you aggregate with the convenient bool_or function

Related

In PostgreSQL, how can I extract matching items from a list?

I have a query in PostgreSQL that returns results like this, records with a string and a json array:
id | property_list
-----+-------------------------------------------------------------------------------
"i1" | [{"a":{"b":"no"}}, {"a":{"b":"yes"}}, {"a":{"b":"true"}}, {"a":{"b":"false"}}]
"i2" | [{"a":{"b":"yes"}}, {"a":{"b":"no"}}, {"a":{"b":"no"}}]
What I need is something like this:
id | yes_or_true
-----+------------
"i1" | 2
"i2" | 1
I need to count the properties in property_list where a.b equals "yes" or "true".
There are more properties, but there is always an a.b property with a string as its value.
I can solve this using a PL/pgSQL function, but for some reason, I'm in a situation where I can't use a PL/pgSQL function. How can I solve this in the query?
You can do this using jsonb_array_elements and a subquery:
SELECT
id,
(SELECT count(*)
FROM json_array_elements(property_list) el
WHERE el->'a'->>'b' IN ('true','yes')
) AS yes_or_true
FROM the_table
A lateral join to jsonb_array_elements() will solve this:
with indat (id, property_list) as (
values
('i1', '[{"a":{"b":"no"}}, {"a":{"b":"yes"}}, {"a":{"b":"true"}}, {"a":{"b":"false"}}]'::jsonb),
('i2', '[{"a":{"b":"yes"}}, {"a":{"b":"no"}}, {"a":{"b":"no"}}]'::jsonb)
)
select id, count(*) filter (where jdat->'a'->>'b' in ('yes', 'true'))
from indat
cross join lateral jsonb_array_elements(property_list) as j(jdat)
group by id;
id | count
----+-------
i1 | 2
i2 | 1
(2 rows)

PostgreSQL: Efficiently aggregate array columns as part of a group by

We wish to perform a GROUP BY operation on a table. The original table contains an ARRAY column. Within a group, the content of these arrays should be transformed into a single array with unique elements. No ordering of these elements is required. contain
Newest PostgreSQL versions are available.
Example original table:
id | fruit | flavors
---: | :----- | :---------------------
| apple | {sweet,sour,delicious}
| apple | {sweet,tasty}
| banana | {sweet,delicious}
Exampled desired result:
count_total | aggregated_flavors
----------: | :---------------------------
1 | {delicious,sweet}
2 | {sour,tasty,delicious,sweet}
SQL toy code to create the original table:
CREATE TABLE example(id int, fruit text, flavors text ARRAY);
INSERT INTO example (fruit, flavors)
VALUES ('apple', ARRAY [ 'sweet','sour', 'delicious']),
('apple', ARRAY [ 'sweet','tasty' ]),
('banana', ARRAY [ 'sweet', 'delicious']);
We have come up with a solution requiring transforming the array to s
SELECT COUNT(*) AS count_total,
array
(SELECT DISTINCT unnest(string_to_array(replace(replace(string_agg(flavors::text, ','), '{', ''), '}', ''), ','))) AS aggregated_flavors
FROM example
GROUP BY fruit
However we think this is not optimal, and may be problematic as we assume that the string does neither contain "{", "}", nor ",". It feels like there must be functions to combine arrays in the way we need, but we weren't able to find them.
Thanks a lot everyone!
demo:db<>fiddle
Assuming each record contains a unique id value:
SELECT
fruit,
array_agg(DISTINCT flavor), -- 2
COUNT(DISTINCT id) -- 3
FROM
example,
unnest(flavors) AS flavor -- 1
GROUP BY fruit
unnest() array elements
Group by fruit value: array_agg() for distinct flavors
Group by fruit value: COUNT() for distinct ids with each fruit group.
if the id column is really empty, you could generate the id values for example with the row_number() window function:
demo:db<>fiddle
SELECT
*
FROM (
SELECT
*, row_number() OVER () as id
FROM example
) s,
unnest(flavors) AS flavor

Filter a top level JSONB array document in Postgres?

I have a Postgres table named 'events' with a column of 'id', 'name', 'invoices':
Table "public.events"
Column | Type | Modifiers
-----------------------+-----------------------------+---------------------------------------------------------------
id | integer | not null default nextval('events_id_seq'::regclass)
name | character varying(255) | not null
invoices | jsonb | not null
Each row has a invoices column of type jsonb with an array as a top level element:
Example Content:
[{"InvoiceId":4,"CustomerId":14,"InvoiceDate":"2009-01-06T00:00:00","BillingAddress":"8210 111 ST NW","BillingCity":"Edmonton","BillingState":"AB","BillingCountry":"Canada","BillingPostalCode":"T6G 2C7","Total":8.91},
{"InvoiceId":5,"CustomerId":23,"InvoiceDate":"2009-01-11T00:00:00","BillingAddress":"69 Salem Street","BillingCity":"Boston","BillingState":"MA","BillingCountry":"USA","BillingPostalCode":"2113","Total":13.86},
{"InvoiceId":8,"CustomerId":40,"InvoiceDate":"2009-02-01T00:00:00","BillingAddress":"8, Rue Hanovre","BillingCity":"Paris","BillingState":null,"BillingCountry":"France","BillingPostalCode":"75002","Total":1.98},
{"InvoiceId":9,"CustomerId":42,"InvoiceDate":"2009-02-02T00:00:00","BillingAddress":"9, Place Louis Barthou","BillingCity":"Bordeaux","BillingState":null,"BillingCountry":"France","BillingPostalCode":"33000","Total":3.96},
{"InvoiceId":10,"CustomerId":46,"InvoiceDate":"2009-02-03T00:00:00","BillingAddress":"3 Chatham Street","BillingCity":"Dublin","BillingState":"Dublin","BillingCountry":"Ireland","BillingPostalCode":null,"Total":5.94},
{"InvoiceId":11,"CustomerId":52,"InvoiceDate":"2009-02-06T00:00:00","BillingAddress":"202 Hoxton Street","BillingCity":"London","BillingState":null,"BillingCountry":"United Kingdom","BillingPostalCode":"N1 5LH","Total":8.91},
{"InvoiceId":13,"CustomerId":16,"InvoiceDate":"2009-02-19T00:00:00","BillingAddress":"1600 Amphitheatre Parkway","BillingCity":"Mountain View","BillingState":"CA","BillingCountry":"USA","BillingPostalCode":"94043-1351","Total":0.99},
{"InvoiceId":14,"CustomerId":17,"InvoiceDate":"2009-03-04T00:00:00","BillingAddress":"1 Microsoft Way","BillingCity":"Redmond","BillingState":"WA","BillingCountry":"USA","BillingPostalCode":"98052-8300","Total":1.98},
{"InvoiceId":15,"CustomerId":19,"InvoiceDate":"2009-03-04T00:00:00","BillingAddress":"1 Infinite Loop","BillingCity":"Cupertino","BillingState":"CA","BillingCountry":"USA","BillingPostalCode":"95014","Total":1.98},
{"InvoiceId":16,"CustomerId":21,"InvoiceDate":"2009-03-05T00:00:00","BillingAddress":"801 W 4th Street","BillingCity":"Reno","BillingState":"NV","BillingCountry":"USA","BillingPostalCode":"89503","Total":3.96}]
How do i return a request set with a list of each rows id, name, and array of invoices that have a "Total" Greater than 5.00?
select id, name, invoices
from
events
cross join lateral
jsonb_array_elements(invoices) o (o)
group by 1,2,3
having sum((o ->> 'Total')::numeric > 5)

TSQL: Extract XML Nested Tags Into Columns

Using SQLServer2008R2
I currently have XML tags with data inside the XML tags (not between them), such as:
<zooid="1"><animals key="all" zebras="22" dogs="0" birds="4" /><animals key="all" workers="yes" vacation="occasion" /> ... *(more)*</zooid>
<zooid="2"><animals key="house" zebras="0" dogs="1" birds="2" /><animals key="house" workers="no" vacation="no" /> ... *(more)*</zoodid>
If I query the XML or use the value function against it, it returns blank values because it tries to read between tags - where no value exists. I need it to read inside of the tags, parse out the values before the equal sign as columns and the values between the quotations as values inside those columns (granted, I could create a function that could do this, but this would be quite meticulous, and I'm curious if something like this already exists). What it should look like this in columns:
Key | Zebras | Dogs | Birds | Key | Workers | Vacation | ... *(more)*
... and this in rows of data:
all | 22 | 0 | 4 | all | yes | occasion | ... *(more)*
house | 0 | 1 | 2 | house | no | no | ... *(more)*
So the final output (just using the two XML rows from the beginning for now), would look like the below data in table form:
Key | Zebras | Dogs | Birds | Key | Workers | Vacation | ... *(more)*
================================================================
all | 22 | 0 | 4 | all | yes | occasion | ... *(more)*
house | 0 | 1 | 2 | house | no | no | ... *(more)*
Other than querying against XML, using the .query tool and even trying the .node tool (using CROSS APPLY see this thread), I haven't been able to generate this.
Try this one -
DECLARE #YourXML NVARCHAR(MAX)
SELECT #YourXML = '
<zooid="1">
<animals key="all" zebras="22" dogs="0" birds="4" />
<animals key="all" workers="yes" vacation="occasion" />
</zooid>
<zooid="2">
<animals key="house" zebras="0" dogs="1" birds="2" />
<animals key="house" workers="no" vacation="no" />
</zoodid>'
DECLARE #XML XML
SELECT #XML =
REPLACE(
REPLACE(#YourXML, 'zooid=', 'zooid id=')
, '</zoodid>'
, '</zooid>')
SELECT
d.[Key]
, Dogs = MAX(d.Dogs)
, Zebras = MAX(d.Zebras)
, Birds = MAX(d.Birds)
, Workers = MAX(d.Workers)
, Vacation = MAX(d.Vacation)
FROM (
SELECT
[Key] = t.p.value('./#key', 'NVARCHAR(50)')
, Zebras = t.p.value('./#zebras', 'INT')
, Dogs = t.p.value('./#dogs', 'INT')
, Birds = t.p.value('./#birds', 'INT')
, Workers = t.p.value('./#workers', 'NVARCHAR(20)')
, Vacation = t.p.value('./#vacation', 'NVARCHAR(20)')
FROM #XML.nodes('/zooid/animals') t(p)
) d
GROUP BY d.[Key]
Your xml appears invalid. How are you able to specify an element like this: ? Generally xml structure is <(elementName) (Attribute)="(Value)"/>. Unless I am mistaken if you are casting text to xml the way it is it will fail. Saying that I can show a working example for proper xml in a self extracting example that will run in SQL Managment Studio as is.
declare #text1 varchar(max) = '<zooid="1"><animals="all" zebras="22" dogs="0" birds="4" /><animals="all" workers="yes" vacation="occasion" /></zooid>'
, #text2 varchar(max) = '<a zooid="1"><b animals="all" zebras="22" dogs="0" birds="4" /><b animals="all" workers="yes" vacation="occasion" /></a>'
, #xml xml
;
begin try
set #xml = cast(#text1 as xml)
end try
begin catch
set #xml = '<ElementName Attribute="BadData Elements are not named" />'
end catch
select #xml
begin try
set #xml = cast(#text2 as xml)
end try
begin catch
set #xml = '<ElementName Attribute="BadData" />'
end catch
select
#xml.value('(/a/b/#animals)[1]', 'varchar(20)') as AnimalsValue
, #xml.value('(/a/b/#zebras)[1]', 'int') as ZebrasValue
, #xml.value('(/a/b/#dogs)[1]', 'int') as DogsValue
, #xml.value('(/a/b/#birds)[1]', 'int') as BirdsValue
, #xml.value('(/a/b/#workers)[1]', 'varchar(16)') as Workers
, #xml.value('(/a/b/#vacation)[1]', 'varchar(16)') as Vacation
The '.value' method is a syntax for querying xml in SQL. I am basically finding the elements(I did generics of a that contained b). Then once at the level I want '#animals' stands for 'attribute of name animals'. The [1] is a position since I can only return one thing at a time, so I chose the first position. Then it needs to a datatype to return. Text is varchar and numbers are ints.
XML query methods: http://msdn.microsoft.com/en-us/library/ms190798.aspx

Query XML field with T-SQL

How can I query multiple nodes in XML data with T-SQL and have the result output to a single comma separated string?
For example, I'd like to get a list of all the destination names in the following XML to look like "Germany, France, UK, Italy, Spain, Portugal"
<Holidays>
<Summer>
<Regions>
<Destinations>
<Destination Name="Germany" />
<Destination Name="France" />
<Destination Name="UK" />
<Destination Name="Italy" />
<Destination Name="Spain" />
<Destination Name="Portugal" />
</Destinations>
<Regions>
</Summer>
</Holidays>
I was trying something like:
Countries = [xmlstring].value('/Holidays/Summer/Regions/Destinations/#Name', 'varchar')
First, to get a list of records from a source XML table, you need to use the .nodes function (DEMO):
select Destination.value('data(#Name)', 'varchar(50)') as name
from [xmlstring].nodes('/Holidays/Summer/Regions/Destinations/Destination')
D(Destination)
Sample output:
| NAME |
-------------
| Germany |
| France |
| UK |
| Italy |
| Spain |
| Portugal |
From here, you want to concatenate the destination values into a comma-separated list. Unfortunately, this is not directly supported by T-SQL, so you'll have to use some sort of workaround. If you're working with a source table using multiple rows, the simplest method is the FOR XML PATH('') trick. In this query I use a source table called Data, and split out the XML into separate records, which I then CROSS APPLY with FOR XML PATH('') to generate comma-separated rows. Finally, the final , is stripped from the result to create the list (DEMO):
;with Destinations as (
select id, name
from Data
cross apply (
select Destination.value('data(#Name)', 'varchar(50)') as name
from [xmlstring].nodes('/Holidays/Summer/Regions/Destinations/Destination') D(Destination)
) Destinations(Name)
)
select id, substring(NameList, 1, len(namelist) - 1)
from Destinations as parent
cross apply (
select name + ','
from Destinations as child
where parent.id = child.id
for xml path ('')
) DestList(NameList)
group by id, NameList
Sample Output (Note that I've added another XML fragment to the test data to make a more complex example):
| ID | COLUMN_1 |
-----------------------------------------------
| 1 | Germany,France,UK,Italy,Spain,Portugal |
| 2 | USA,Australia,Brazil |

Resources