Preserving Array Member Order in Postgres Query - sql
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)
Related
Can I have SQL incrementally count XML elements while parsing?
So this is my first foray into parsing XML, and I'm trying to figure out how to get this to work how I want. Given the following XML format: <Tiles> <TileRow> <TileValue>2</TileValue> <TileValue>3</TileValue> <TileValue>4</TileValue> </TileRow> <TileRow> <TileValue>2</TileValue> <TileValue>7</TileValue> </TileRow> </Tiles> I want it to put it in a SQL table as the following: | X | Y | Val | |---|---|-----| | 1 | 1 | 2 | | 1 | 2 | 3 | | 1 | 3 | 4 | | 2 | 1 | 2 | | 2 | 2 | 7 | Basically, imagine a grid, and each "TileRow" starts a new Row in that grid. Each "TileValue" assigns the Column position in that grid, with the actual TileValue being what's in the 'cell' in that grid. Is there a way to make SQL 'count' each time it passes over an element, or something to that effect?
Please try the following solution. It is based on the Node Order Comparison operator in XQuery. Node Order Comparison Operators SQL DECLARE #xml XML = N'<Tiles> <TileRow> <TileValue>2</TileValue> <TileValue>3</TileValue> <TileValue>4</TileValue> </TileRow> <TileRow> <TileValue>2</TileValue> <TileValue>7</TileValue> </TileRow> </Tiles>'; SELECT c.value('for $i in . return count(/Tiles/TileRow[. << $i])', 'INT') AS [X] , c.value('for $i in . return count(../*[. << $i]) + 1', 'INT') AS [Y] , c.value('(./text())[1]', 'INT') as Value FROM #xml.nodes('/Tiles/TileRow/TileValue') AS t(c); Output +---+---+-------+ | X | Y | Value | +---+---+-------+ | 1 | 1 | 2 | | 1 | 2 | 3 | | 1 | 3 | 4 | | 2 | 1 | 2 | | 2 | 2 | 7 | +---+---+-------+
Unfortunately, SQL Server does not support returning position() directly, it only allows it inside a predicate. If it did then you could simply query: select v.TileValue.value('position(parent::*)[1]','int'), v.TileValue.value('./position()[1]','int'), v.TileValue.value('text()[1]','int') from #x.nodes('/Tiles/TileRow/TileValue') v(TileValue) Instead, you can simulate it with ROW_NUMBER() select v1.rn, row_number() over (partition by v1.rn order by (select 1)), v2.TileValue.value('text()[1]','int') from ( select v1.TileRow.query('.') TileRow, row_number() over (order by (select 1)) rn from #x.nodes('/Tiles/TileRow') v1(TileRow) ) v1 cross apply v1.TileRow.nodes('TileRow/TileValue') v2(TileValue)
How to select oldest date row from each product using SQL
I would like to get the oldest only one from every type product and sum of the prices listed in listofproduct table. Another thing is to search only between prodacts that has at least one peace on lager. With the SQL I managed to get all the products has at least one on the stock. But the rest I am stack... So the sum cold be done later, that was my plan, but if you have better idea feel free to write Here is my data: +-------------+----------------+---------------+----------+ | IDProizvoda | NazivProizvoda | DatumKupovine | NaLageru | +-------------+----------------+---------------+----------+ | 77 | Cokolada | 25-Feb-20 | 2 | | 44 | fgyhufrthr | 06-Aug-20 | 5 | | 55 | Auto | 06-Aug-23 | 0 | | 55 | Auto | 11-Aug-20 | 200 | | 77 | Cokolada | 06-Aug-27 | 0 | | 77 | Cokolada | 25-Feb-20 | 10 | | 77 | Cokolada | 25-Jan-20 | 555 | | 77 | Cokolada | 25-Mar-20 | 40 | +-------------+----------------+---------------+----------+ Access.ExeQuery("SELECT * FROM Products " & "WHERE IDProizvoda IN (SELECT value FROM STRING_SPLIT(#listofproduct, ',')) " & "AND NaLageru > 0 ") I tried to add GROUP BY and HAVING but it does not worked because i choose the whole table. But I need Product ID and Stock field for edit it later, to subtract one from the stock for those products. I would like to get the result: +-------------+----------------+---------------+----------+ | IDProizvoda | NazivProizvoda | DatumKupovine | NaLageru | +-------------+----------------+---------------+----------+ | 44 | fgyhufrthr | 06-Aug-20 | 5 | | 55 | Auto | 11-Aug-20 | 200 | | 77 | Cokolada | 25-Jan-20 | 555 | +-------------+----------------+---------------+----------+ Thank you for all the help.
You can do it with a Cross Apply, this would be your SQL query: Select P.IDProizvoda, P.NazivProizvoda, N.DatumKupovine, N.NaLageru, N.IDKupovine, N.CenaPoKomadu From products P Cross Apply ( Select top 1 DatumKupovine, NaLageru, IDKupovine, CenaPoKomadu From products P2 where P2.IDProizvoda = P.IDProizvoda and P2.NaLageru > 0 order by DatumKupovine ) N group by P.IDProizvoda, P.NazivProizvoda, N.DatumKupovine, N.NaLageru, N.IDKupovine, N.CenaPoKomadu And this your ExeQuery: Access.ExeQuery("Select P.IDProizvoda, P.NazivProizvoda, N.DatumKupovine, N.NaLageru, N.IDKupovine, N.CenaPoKomadu From products P " & " Cross Apply( Select top 1 DatumKupovine, NaLageru, IDKupovine, CenaPoKomadu From products P2 where P2.IDProizvoda = P.IDProizvoda and P2.NaLageru > 0 order by DatumKupovine) N " & " where P.IDProizvoda in (Select value From STRING_SPLIT(#listofproduct, ',')) " & " group by P.IDProizvoda, P.NazivProizvoda, N.DatumKupovine, N.NaLageru, N.IDKupovine, N.CenaPoKomadu " )
I think this is just aggregation with a filter: SELECT IDProizvoda, NazivProizvoda, MAX(DatumKupovine), SUM(NaLegaru) FROM Products p WHERE NaLegaru > 0 GROUP BY IDProizvoda, NazivProizvoda;
This should do it: with cte as ( SELECT *, row_number() over ( partition by NazivProizvoda order by DatumKupovine ) as rn FROM Products WHERE IDProizvoda IN ( SELECT value FROM STRING_SPLIT(#listofproduct, ',') ) AND NaLageru > 0 ) select * from cte where rn = 1; By way of explanation, I'm using a common table expression to select the superset of the data you want by criteria and adding a column that enumerates each row within a group (a group being defined here as having NazivProizvoda be the same) in order of the DatumKupovine). With that done, anything that admits the value of 1 for that enumeration will be the oldest in the group. If you data is such that more than one row can be the oldest, use rank() instead of row_number().
SELECT 1 ID and all belonging elements
I try to create a json select query which can give me back the result on next way. 1 row contains 1 main_message_id and belonging messages. (Like the bottom image.) The json format is not a requirement, if its work with other methods, it will be fine. I store the data as like this: +-----------------+---------+----------------+ | main_message_id | message | sub_message_id | +-----------------+---------+----------------+ | 1 | test 1 | 1 | | 1 | test 2 | 2 | | 1 | test 3 | 3 | | 2 | test 4 | 4 | | 2 | test 5 | 5 | | 3 | test 6 | 6 | +-----------------+---------+----------------+ I would like to create a query, which give me back the data as like this: +-----------------+-----------------------+--+ | main_message_id | message | | +-----------------+-----------------------+--+ | 1 | {test1}{test2}{test3} | | | 2 | {test4}{test5}{test6} | | | 3 | {test7}{test8}{test9} | | +-----------------+-----------------------+--+
You can use json_agg() for that: select main_message_id, json_agg(message) as messages from the_table group by main_message_id; Note that {test1}{test2}{test3} is invalid JSON, the above will return a valid JSON array e.g. ["test1", "test2", "test3"] If you just want a comma separated list, use string_agg(); select main_message_id, string_ag(message, ', ') as messages from the_table group by main_message_id;
ORDER BY FIELD LIST - Subquery returns more than 1 row
What i want to do is quite simple: Write an SQL that will return a bunch of record and order the records by some list of id from the FIELD LIST section of my SQL TABLE SAMPLE lessons +----+----------------------+ | id | name | +----+----------------------+ | 9 | Greedy algorithms | | 5 | Maya civilization | | 3 | eFront Beginner | | 2 | eFront Intermediate | +----+----------------------+ mod_comp_rule +----+---------------------+ | id | lesson_id | comp_id | +----+---------------------+ | 1 | 3 | 1 | | 2 | 2 | 1 | | 3 | 9 | 2 | +----+---------------------+ WHAT I WANT TO GET FROM MY QUERY SELECT * FROM lessons ORDER BY FIELD(id,'3','2','9') ASC; MY SQL SELECT ls.id, ls.name FROM lessons ls ORDER BY FIELD(ls.id, (SELECT mcr.lesson_id FROM mod_comp_rule mcr INNER JOIN lessons ls ON ls.id = mcr.lesson_id)) My SQL Query returned the following error MySQL said: #1242 - Subquery returns more than 1 row So how can i make my SQL return FIELD(id,'3','2','9') without flagging the more than 1 row error ?
I don't see why FIELD() is needed for this. A correlated query will do what you want: SELECT ls.id, ls.name FROM lessons ls ORDER BY (SELECT mcr.id FROM mod_comp_rule mcr WHERE ls.id = mcr.lesson_id);
Pick a record based on a given value in postgres
I have a table in postgres like below, alg_campaignid | alg_score | cp | sum ----------------+-----------+---------+---------- 9829 | 30.44056 | 12.4000 | 12.4000 9880 | 29.59280 | 12.0600 | 24.4600 9882 | 29.59280 | 12.0600 | 36.5200 9827 | 29.27504 | 11.9300 | 48.4500 9821 | 29.14840 | 11.8800 | 60.3300 9881 | 29.14840 | 11.8800 | 72.2100 9883 | 29.14840 | 11.8800 | 84.0900 10026 | 28.79280 | 11.7300 | 95.8200 10680 | 10.31504 | 4.1800 | 100.0000 From which i have to select a record based on randomly generated number from 0 to 100.i.e first record should be returned if random number picked is between 0 and 12.4000,second if rendom is between 12.4000 and 24.4600,and likewise last if random no is between 95.8200 and 100.0000. For Example if the random number picked is 8 then the first record should be returned or if the random number picked is 48 then the fourth record should be returned Is it possible to do this postgres if so kindly recommend a solution for this..
Yes, you can do this in Postgres. If you want to generate the number in the database: with r as ( select random() * 100 as r ) select t.* from table t cross join r where t.sum <= r.r order by t.sum desc limit 1;