PostgreSQL - left join generate_series() and table - sql

I use generate series to create a series of numbers from 0 to 200.
I have a table that contains dirtareas in mm² in a column called polutionmm2. What I need is to left join this table to the generated series, but the dirt area must be in cm² so /100. I was not able to make this work, as I can't figure out how I can connect a table to a series that has no name.
This Is what I have so far:
select generate_series(0,200,1) as x, cast(p.polutionmm2/100 as char(8)) as metric
from x
left join polutiondistributionstatistic as p on metric = x
error: relation X does not exist
Here is some sample data: https://dbfiddle.uk/?rdbms=postgres_13&fiddle=3d7d851887adb938819d6cf3e5849719
what I would need, is the first column (x) counting all the way from 0 to 200, and where there is a matching value, to show it in the second column.
Like this:
x, metric
0, 0
1, 1
2, 2
3, null
4, 4
5, null
... , ...
... , ...
200, null

You can put generate_series() in the FROM. So, I think you want something like this:
select gs.x, cast(p.polutionmm2/100 as char(8)) as metric
from generate_series(0,200,1) gs(x) left join
p
on gs.x = (p.polutionmm2/100);
I imagine there is also more to your query, because this doesn't do much that is useful.

Related

How to extract a json value inside an array inside a json in SQL

I have a JSON being returned in my query called MetaDataJSON, inside which is an array of JSONs called Results. Inside each JSON in Results are two values, Chronic and Probability. There are a couple other tables that have been joined too. Is there a way to get Chronic in a column by itself? Right now I have gotten this far (table and variable names have been made generic):
SELECT DISTINCT
JSON_QUERY(mdj.value, '$.Results[0]') [Results]
FROM table1 t1
JOIN table2 t2 ON t2.parameter1 = t1.parameter1
AND t2.parameter2 = 'ASDF'
JOIN table3 t3 ON oad.parameter3 = oa.parameter3
AND t3.parameter4 = 11
CROSS APPLY OPENJSON(t3.MetaDataJSON) as mdj
This gets me a column called Results where each entry looks like:
{"Chronic": 0, "Probability": 0.0016}
Is there an efficient way to get Chronic in a column by itself? Thanks!
You can do it like this,
WITH jsons (x)
AS
(
-- replace this part with your query
Select a.x from (select '{"Chronic": 0, "Probability": 0.0016}' x ) as a, (SELECT 1 as y union select 2 as y ) as b
)
select
(SELECT value FROM OPENJSON(x,'$') where [key]='Chronic') as "Chronic",
(SELECT value FROM OPENJSON(x,'$') where [key]='Probability') as "Probability"
from jsons;
I think you can change the #json equation to use your query. But I cannot try since I don't have your tables...
BTW, I assume you are using MSSQL...

SQL - WHERE (X, Y) IN (A, B)

I have some kind of blockage currently.
My theoretic query looks something like this:
SELECT * FROM Table WHERE X in (a, b, c) AND Y IN (d, e, f)
So basically, I want all rows having multiple columns match, meaning:
X, Y
1, 2
3, 4
5, 6
7, 8,
9, 10
If I want to get all rows where (X=1, Y=2) or (X=5, Y=6), so X and Y are correlated, how would I do that?
(MS SQL2005+)
Why not something simple like the following?
WHERE (X = 1 AND Y = 2) OR (X = 5 AND Y = 6) ...
Or, if you're looking for rows (based on your example) where Y should be X + 1, then:
WHERE Y = X + 1
If you have thousands of OR clauses like the above, then I would suggest you populate a criterion table ahead of time, and rewrite your query as a join. Suppose you have such a table Criteria(X, Y) then your query becomes much simpler:
SELECT Table.*
FROM Table
INNER JOIN Criteria ON Table.X = Criteria.X AND Table.Y = Criteria.Y
Don't forget to add an index / foreign keys as necessary to the new table.
If for some reason you prefer to not create a table ahead of time, you can use a temporary table or table variable and populate it within your procedure.
If X and Y are in a table then a JOIN would be cleanest:
SELECT * FROM Table t
INNER JOIN XandY xy
WHERE tX = xy.X AND t.Y = xy.Y
If there not in a table I would strongly suggest putting them in one. IN only works with single-value sets and there's no way to line up results using multiple IN clauses.

Coalescing a subset to its parent set

I have two tables, A and B. B is a random subset of A but with some values that override the default values in A. How do I join the two tables to coalesce their values?
A
1, 0
2, 0
3, 0
4, 0
B
2, 10
3, 11
Output
1, 0
2, 10
3, 11
4, 0
Here is my actual query - I thought I could do this with LEFT OUTER JOIN, but this restricts the Output set to the intersection of A and B rows. I need all A rows to return, coalesced with the relevant B rows.
SELECT A.factor, A.categorical_value, coalesce(A.positive, B.positive), coalesce(A.negative, B.negative)
FROM features A
LEFT OUTER JOIN profiles B ON (A.factor=B.factor AND A.categorical_value=B.categorical_value)
WHERE B.uuid='9e5083da74305628336631da9d2903e3'
As Craig Ringer points out below, I am inadvertently restricting A with my B clause. But then how do I do this? Table A is a many-to-many table of profile attributes, where uuid indicates the user id. Table B is a master list of all possible profile attributes. I want the query to return the master list with the an individual profile superimposed on to it.
After question update:
As #Craig already informed you, a WHERE condition on B would only select matching rows in B and act like a [INNER] JOIN instead of a LEFT [OUTER] JOIN.
You need to pull that WHERE condition up into the condition of the LEFT JOIN.
While being at it, I opportunistically simplified with USING, since all joining column names are identical. Details in the manual.
Information is still incomplete and contradicting. Here is another educated guess:
SELECT a.factor, a.categorical_value
, COALESCE(a.positive, b.positive) AS positive
, COALESCE(a.negative, b.negative) AS negative
FROM features a
LEFT JOIN profiles b USING (factor, categorical_value, uuid)
WHERE a.uuid='9e5083da74305628336631da9d2903e3'
Not sure if you need to join on udid, too.
Your example would indicate COALESCE(b.positive, a.positive). Something does not add up ...
More updates in comment
Adapt your JOIN condition then:
SELECT a.factor, a.categorical_value
, COALESCE(a.positive, b.positive) AS positive
, COALESCE(a.negative, b.negative) AS negative
FROM features a
LEFT JOIN profiles b ON a.factor = b.factor
AND a.categorical_value = b.categorical_value
AND b.uuid='9e5083da74305628336631da9d2903e3';

MSSQL - howto to get full list from interval of IDs

I have two tables
Created_Labels:
IF label_ID_from = label_ID_to (it means it has been only one label created), in column label_number is number of created label.
IF label_ID_from <> label_ID_to (more labels was created), in column label_number is NULL and in next two columns is interval of created labels with ID from table bellow.
Labels (list of existing lables):
How can I get the complete list of created label_numbers (get labels with ID 105, 110, 111, 112..120, 200, 201, 202..210, 394, 554)?
SELECT
L.ID
, L.label_number
FROM
Labels L
JOIN
Created_Labels CL
ON
L.ID BETWEEN CL.label_ID_from
AND CL.label_ID_to
Did you try this?
SELECT distinct label_number
FROM created_labeles c;
OR
SELEC distinct l.label_number
FROM created_labeles c,
labels l
WHERE c.label_number = l.label_numbers(+)
AND c.label_number is null
The above second query is the left outer join equivalent in Oracle SQl.

Selecting elements that don't exist

I am working on an application that has to assign numeric codes to elements. This codes are not consecutives and my idea is not to insert them in the data base until have the related element, but i would like to find, in a sql matter, the not assigned codes and i dont know how to do it.
Any ideas?
Thanks!!!
Edit 1
The table can be so simple:
code | element
-----------------
3 | three
7 | seven
2 | two
And I would like something like this: 1, 4, 5, 6. Without any other table.
Edit 2
Thanks for the feedback, your answers have been very helpful.
This will return NULL if a code is not assigned:
SELECT assigned_codes.code
FROM codes
LEFT JOIN
assigned_codes
ON assigned_codes.code = codes.code
WHERE codes.code = #code
This will return all non-assigned codes:
SELECT codes.code
FROM codes
LEFT JOIN
assigned_codes
ON assigned_codes.code = codes.code
WHERE assigned_codes.code IS NULL
There is no pure SQL way to do exactly the thing you want.
In Oracle, you can do the following:
SELECT lvl
FROM (
SELECT level AS lvl
FROM dual
CONNECT BY
level <=
(
SELECT MAX(code)
FROM elements
)
)
LEFT OUTER JOIN
elements
ON code = lvl
WHERE code IS NULL
In PostgreSQL, you can do the following:
SELECT lvl
FROM generate_series(
1,
(
SELECT MAX(code)
FROM elements
)) lvl
LEFT OUTER JOIN
elements
ON code = lvl
WHERE code IS NULL
Contrary to the assertion that this cannot be done using pure SQL, here is a counter example showing how it can be done. (Note that I didn't say it was easy - it is, however, possible.) Assume the table's name is value_list with columns code and value as shown in the edits (why does everyone forget to include the table name in the question?):
SELECT b.bottom, t.top
FROM (SELECT l1.code - 1 AS top
FROM value_list l1
WHERE NOT EXISTS (SELECT * FROM value_list l2
WHERE l2.code = l1.code - 1)) AS t,
(SELECT l1.code + 1 AS bottom
FROM value_list l1
WHERE NOT EXISTS (SELECT * FROM value_list l2
WHERE l2.code = l1.code + 1)) AS b
WHERE b.bottom <= t.top
AND NOT EXISTS (SELECT * FROM value_list l2
WHERE l2.code >= b.bottom AND l2.code <= t.top);
The two parallel queries in the from clause generate values that are respectively at the top and bottom of a gap in the range of values in the table. The cross-product of these two lists is then restricted so that the bottom is not greater than the top, and such that there is no value in the original list in between the bottom and top.
On the sample data, this produces the range 4-6. When I added an extra row (9, 'nine'), it also generated the range 8-8. Clearly, you also have two other possible ranges for a suitable definition of 'infinity':
-infinity .. MIN(code)-1
MAX(code)+1 .. +infinity
Note that:
If you are using this routinely, there will generally not be many gaps in your lists.
Gaps can only appear when you delete rows from the table (or you ignore the ranges returned by this query or its relatives when inserting data).
It is usually a bad idea to reuse identifiers, so in fact this effort is probably misguided.
However, if you want to do it, here is one way to do so.
This the same idea which Quassnoi has published.
I just linked all ideas together in T-SQL like code.
DECLARE
series #table(n int)
DECLARE
max_n int,
i int
SET i = 1
-- max value in elements table
SELECT
max_n = (SELECT MAX(code) FROM elements)
-- fill #series table with numbers from 1 to n
WHILE i < max_n BEGIN
INSERT INTO #series (n) VALUES (i)
SET i = i + 1
END
-- unassigned codes -- these without pair in elements table
SELECT
n
FROM
#series AS series
LEFT JOIN
elements
ON
elements.code = series.n
WHERE
elements.code IS NULL
EDIT:
This is, of course, not ideal solution. If you have a lot of elements or check for non-existing code often this could cause performance issues.