How to perform the following query with Postgres? - sql

This is the example
banzai=# select letter_id, length_id, word from words;
letter_id | length_id | word
-----------+-----------+-------
1 | 1 | run
3 | 1 | tea
2 | 1 | cat
2 | 2 | cast
2 | 3 | coast
1 | 3 | roast
1 | 2 | rest
3 | 2 | team
3 | 3 | toast
(9 rows)
banzai=# select letter from letters;
letter
--------
R
C
T
(3 rows)
banzai=# select length from lengths;
length
--------
4
5
3
(3 rows)
banzai=# select length, letter, word from words, lengths, letters where words.length_id = lengths.id and words.letter_id = letters.id;
length | letter | word
--------+--------+-------
3 | C | cat
3 | R | run
3 | T | tea
4 | R | rest
4 | C | cast
4 | T | team
5 | R | roast
5 | C | coast
5 | T | toast
(9 rows)
I want to produce the following table in HTML
R T C
3 run tea cat
4 rest team cast
5 roast toast coast
I have a method in my java (backend) code that will produce the data in json. Angularjs (frontend) will take the json and present the table in html

As you want JSON this will return a single object:
select json_object_agg(length, o)
from (
select length, json_object_agg(letter, word) as o
from
words w
inner join
lengths l on w.length_id = l.id
inner join
letters t on w.letter_id = t.id
group by length
) s;
json_object_agg
----------------------------------------------------------------------------------------------------------------------------------------------------------------
{ "4" : { "R" : "rest", "C" : "cast", "T" : "team" }, "5" : { "R" : "roast", "C" : "coast", "T" : "toast" }, "3" : { "C" : "cat", "R" : "run", "T" : "tea" } }
The above query is for 9.4. In 9.3 it is a bit more difficult but it can be done as well.

Related

SQLite How do i join two tables

I have two tables that contains translations of different words in english and french
words : contains all the words used in the app
+----+-------+---------+--
| ID | TEXT | LANG_ID |
+----+-------+---------+--
| 1 | partir | 4 |
| 2 | manger | 4 |
| 3 | go | 5 |
| 4 | eat | 5 |
+----+-------+---------+--
Translated_word : contains the translations (english to french and vice versa)
+----+-------+---------+--
| ID | SOURCE | TO |
+----+-------+---------+--
| 10 | 1 | 3 |
| 12 | 2 | 4 |
| 13 | 3 | 1 |
| 14 | 4 | 2 |
+----+-------+---------+--
I need to get all the contents of the word table in this format
1 partir, go
2 manger, eat
3 go, partir
4 eat, manger
I have tried several queries and i am not able to achieve the desired results. For example, when i try this one
SELECT lp.id, tw.id, lp.text, tw.text FROM words AS lp JOIN words AS tw ON (tw.id = lp.id)
i get the following results
1 partir, partir
2 manger, manger
...
...
Can someone let me know what i am doing wrong ?
Thanks
This would use two joins:
select tw.id, w1.*, w2.*
from translated_word tw join
words w1
on tw.source = w1.id join
words w2
on tw.to = w2.id and w2.lang_id <> w1.lang_id
where lang_id in (4, 5);
Note the additional condition on the second join . . . the language ids are not equal. I might expect that you want English and French in different columns. For that:
select tw.id, w1.*, w2.*
from translated_word tw join
words wf
on tw.source = wf.id and wf.lang_id = 4 join
words we
on tw.to = we.id and we.lang_id = 5
where lang_id in (4, 5);
Join translated_word to 2 copies of words:
select w1.id,
w1.text || ', ' || w2.text
from words w1
inner join translated_word tw on tw.source = w1.id
inner join words w2 on w2.id = tw.`to`
See the demo.

Specifying condition operator (AND/OR) for a column based on another column value in SQL

I have a recipe table with a many-to-many to a recipe_filter table. Here's some sample data:
recipe:
id | name
----+-----------
1 | test 2019
12 | slug-14
8 | dfadsfd
6 | test 4
4 | test 2
11 | slug-11
10 | Testology
13 | slug-15
5 | test 3
14 | slug-16
(10 rows)
recipe_filter_join:
recipeId | recipeFilterId
----------+----------------
1 | 1
2 | 2
3 | 3
4 | 1
6 | 5
7 | 6
8 | 4
9 | 7
6 | 8
14 | 9
14 | 4
5 | 9
5 | 38
filter:
id | slug | name | label
----+----------------------+-------------+----------------
2 | fdsfa | fdsfa | Category
3 | dsfds | dsfds | Category
6 | fdsaf | fdsaf | Category
7 | dfad | dfad | Category
8 | product-spice-2 | Spice #2 | Product
9 | product-spice-3 | Spice #3 | Product
5 | product-spice-4 | Spice #4 | Product
4 | product-spice-5 | Spice #5 | Product
1 | product-spice-6 | Spice #6 | Product
10 | product-spice-1 | Spice #1 | Product
40 | diet-halal | Halal | Diet
38 | diet-keto | Keto | Diet
41 | diet-gluten-free | Gluten free | Diet
37 | diet-vegan | Vegan | Diet
39 | diet-diabetic | Diabetic | Diet
42 | cooking-method-bake | Bake | Cooking method
43 | cooking-method-fry | Fry | Cooking method
44 | cooking-method-steam | Steam | Cooking method
45 | cooking-method-roast | Roast | Cooking method
(19 rows)
The input to my query is a list of filters.slugs for example product-spice-1, product-spice-5, cooking-method-fry, cooking-method-steam.
For the above example, I want to write a query that gets all recipes where the filter slug is (product-spice-1 or product-spice-5) and (cooking-method-fry or cooking-method-steam).
How do I create a generic query from the example above?
Update: In case it's not clear, for the list of filters given, I want to group them based on label and apply an OR between group members and an AND condition for other groups, if that makes any sense.
You want to INTERSECT two queries
SELECT
rfj."recipeId"
FROM recipe_filter_join rfj
JOIN filter ON filter.id = rfj."recipeFilterId"
WHERE filter.slug IN ('product-spice-1','product-spice-5')
INTERSECT
SELECT
rfj."recipeId"
FROM recipe_filter_join rfj
JOIN filter ON filter.id = rfj."recipeFilterId"
WHERE filter.slug IN ('cooking-method-fry', 'cooking-method-steam')
And this is is quite generalizable. As you can see, the only difference between the two parts is in the WHERE clause. If you have other conditions on Diet or category, you could generate the appropriate query string with the variation on filer & join them with INTERSECT as the separator in your programming language of choice.
I want to group them based on label and apply an OR between group members and an AND condition for other groups.
If you would prefer to have your application code call the query with just a list of slugs, then the following solution is more general.
If we restate the problem description as :
We want to search for recipes which have ingredients intersecting with the provided ingredient list, and the distinct labels for the recipes equals the distinct labels derived from the ingredient list (this last part is handled by the having clause)
We can write
WITH distinct_labels AS (
SELECT
ARRAY_AGG(DISTINCT label ORDER BY label) distinct_labels_filtered
FROM filter
WHERE slug IN ('product-spice-1','product-spice-5','cooking-method-fry', 'cooking-method-steam')
)
SELECT
rfj."recipeId"
FROM filter
JOIN recipe_filter_join rfj
ON filter.id = rfj."recipeFilterId"
WHERE slug IN ('product-spice-1','product-spice-5','cooking-method-fry', 'cooking-method-steam')
GROUP BY 1
HAVING ARRAY_AGG(DISTINCT label ORDER BY label) = (SELECT distinct_labels_filtered FROM distinct_labels)

Product in a GROUP BY with potential zeroes

So I searched a bit and found that you can do a product of rows in Oracle using a GROUP BY and a nifty mathematical formula: exp(sum(ln(some_col))). It's pretty awesome, but unfortunately doesn't work when some_col is potentially zero (because ln(0) is not possible as ln(x) is negative infinity as it approaches zero).
Example query:
select
a, b, c
sum(d) d,
sum(e) e,
exp(sum(ln(f))) f
from x
group by a, b, c;
Obviously since this is a product of values, if one of them is zero, the product would be zero. The immediate thought would be to use a case, but that would require the case statement to be on an aggregate value or something in the GROUP BY... which it isn't. I can't just exclude those rows because I still need sum(d) and sum(e).
Any thoughts on a good way to do this while dealing with potential zeroes? I was thinking about something involving over(partition by ...), but in reality, my query groups by 12 columns and there are 20 other columns being aggregated. That query could get reeaaaal ugly, but if it's the only solution, I suppose I don't have a choice.
Side question: is there any particular reason there isn't a product function in Oracle? Seems like it'd be such a basic thing to include like sum is.
Note: This is Oracle 12c.
Example:
If I had an input table like this (matching with the query above):
| a | b | c | d | e | f |
+-----+-----+-----+---+---+---+
| foo | bar | hoo | 1 | 2 | 2 |
| foo | bar | hoo | 3 | 4 | 3 |
| foo | bar | hoo | 2 | 5 | 0 |
| foo | bar | mee | 1 | 2 | 2 |
| foo | bar | mee | 3 | 4 | 3 |
I would expect output like this:
| a | b | c | d | e | f |
+-----+-----+-----+---+----+---+
| foo | bar | hoo | 6 | 11 | 0 |
| foo | bar | mee | 4 | 6 | 6 |
However, because the third row has a 0 for f, we naturally get ORA-01428: argument '0' is out of range for ln(0).
First, log(0) is not undefined - it's negative infinity.
Second: in Oracle you can generate a negative infinity, but you'll have to use BINARY_FLOAT.
select a, b, c,
sum(d) d,
sum(e) e,
exp(sum(CASE WHEN f <> 0 THEN ln(f) ELSE -1/0F END)) f
from x
group by a, b, c;
Using your data this generates:
A B C D E F
foo bar hoo 6 11 0
foo bar mee 4 6 6.0000001304324524E+000
Note that introducing logarithms and power functions will introduce some rounding issues, but this should at least get you started.
dbfiddle here
TO NEGATIVE INFINITY...AND BEYOND!!!!!!
:-)

Preserving Array Member Order in Postgres Query

I would like to know how to preserve/utilize the order of array elements when issuing a select query in Postgres. (In case it's relevant, the array is multidimensional.)
For example, given the following data:
id | points
----+---------------------------------
1 | {{1,3},{7,11},{99,101},{0,1}}
2 | {{99,101},{7,11},{0,1},{77,22}}
I'd like to know how to write a query which finds rows whose points:
contain the subarray {{7, 11}, {99, 101}}
but not {{99, 101},{7, 11}}.
I've tried using various array operators (#>, &&), adding an index using the intarray module, etc. but have not found a workable solution.
to be able to "unnest array by 1 dimention" and use the result set for incomarison, use Pavel Stěhule suggested function:
t=# with c(i,p) as (values(1,'{{1,3},{7,11},{99,101},{0,1}}'::int[][]),(2,'{{99,101},{7,11},{0,1},{77,22}}'))
, p as (select *,a,case when e = '{7, 11}' and lead(e) over (partition by i order by o) = '{99, 101}' and o = lead(o) over (partition by i order by o) -1 then true end from c, reduce_dim(p) with ordinality as a (e,o))
select * from p;
i | p | e | o | a | case
---+---------------------------------+----------+---+----------------+------
1 | {{1,3},{7,11},{99,101},{0,1}} | {1,3} | 1 | ("{1,3}",1) |
1 | {{1,3},{7,11},{99,101},{0,1}} | {7,11} | 2 | ("{7,11}",2) | t
1 | {{1,3},{7,11},{99,101},{0,1}} | {99,101} | 3 | ("{99,101}",3) |
1 | {{1,3},{7,11},{99,101},{0,1}} | {0,1} | 4 | ("{0,1}",4) |
2 | {{99,101},{7,11},{0,1},{77,22}} | {99,101} | 1 | ("{99,101}",1) |
2 | {{99,101},{7,11},{0,1},{77,22}} | {7,11} | 2 | ("{7,11}",2) |
2 | {{99,101},{7,11},{0,1},{77,22}} | {0,1} | 3 | ("{0,1}",3) |
2 | {{99,101},{7,11},{0,1},{77,22}} | {77,22} | 4 | ("{77,22}",4) |
(8 rows)
now, that you see the logic, complete where:
t=# with c(i,p) as (values(1,'{{1,3},{7,11},{99,101},{0,1}}'::int[][]),(2,'{{99,101},{7,11},{0,1},{77,22}}'))
, p as (select *,a,case when e = '{7, 11}' and lead(e) over (partition by i order by o) = '{99, 101}' and o = lead(o) over (partition by i order by o) -1 then true end from c, reduce_dim(p) with ordinality as a (e,o))
select i,p from p where "case";
i | p
---+-------------------------------
1 | {{1,3},{7,11},{99,101},{0,1}}
(1 row)
not to mention that in case of sequential array pair, you can just cast it to text and use like operator:
t=# with c(i,p) as (values(1,'{{1,3},{7,11},{99,101},{0,1}}'::int[][]),(2,'{{99,101},{7,11},{0,1},{77,22}}'))
select * from c where p::text like '%{7,11},{99,101}%';
i | p
---+-------------------------------
1 | {{1,3},{7,11},{99,101},{0,1}}
(1 row)

Most efficient way to query a word & synonym table

I have a WORDTB table with words and their synonyms: ID, WORD1, WORD2, WORD3, WORD4, WORD5. These words are arranged according to their frequency. When any word is given I want to query and retrieve the most frequent synonym of that particular word which is the word in WORD1 column.
This is the query I tried and it works fine, but I think this is inefficient.
SELECT WORD1
FROM WORDTB
WHERE WORD1='xxxx'
OR WORD2='xxxx'
OR WORD3='xxxx'
OR WORD4='xxxx'
OR WORD5='xxxx'
Can anyone suggest a more efficient way of doing this.
A more scalable solution would be to use a single row for each word.
synonym_words(word_id, synonym_id, word, popularity)
Fields:
word_id: The primary key for a word.
synonym_id: The word_id of the first synonym word.
word: The synonym text.
popularity: The sort order for the list of synonyms, 1 being the most popular.
Sample table data:
word_id | synonym_id | word | popularity
==============================================
1 | 1 | start | 1
2 | 1 | begin | 2
3 | 1 | originate | 3
4 | 1 | initiate | 4
5 | 1 | commence | 5
6 | 1 | create | 6
7 | 1 | startle | 7
8 | 1 | leave | 8
9 | 9 | end | 1
10 | 9 | ending | 2
11 | 9 | last | 3
12 | 9 | goal | 4
13 | 9 | death | 5
14 | 9 | conclusion | 6
15 | 9 | close | 7
16 | 9 | closing | 8
Assuming that the words will not change but their popularity may over time, the query should not break if you were to change the popularity order of the words so that the most popular synonym for a word was changed. You want your query to return the most popular word (popularity = 1) which shares the same synonym_id as the word used in the search.
SQL query:
SELECT word FROM synonym_words
WHERE synonym_id = (SELECT synonym_id FROM synonym_words WHERE word = 'conclusion')
AND popularity = 1