Change Postgres select with multiple array_agg and group by - sql

I write SQL in postgres 9.3 which works almost perfectly:
SELECT type_id, to_json(array_agg(row(value, id))) AS json FROM sub_types GROUP BY type_id
The result table looks:
type_id | json
1 | [{"f1":"something", "f2":7}, ...]
2 | [{"f1":"something new", "f2":2}, ...]
I am trying to do that the result looks like:
type_id | json
1 | [{"value":"something", "id":7}, ...]
2 | [{"value":"something new", "id":2}, ...]
Basic idea is to to write code (PHP) something close to this:
rows = pdo_call_select
rows = pdo_call_select
foreach (rows as row)
{
print '<span data-id="row->id">'
foreach (row->json as otherfields)
print '<input value="otherfields->value" ...'
...
and my table is:
id | type_id | value
1 3 something
2 2 blabla
3 3 something new
4 1 ok
...

create table sub_types (
id int, type_id int, value text
);
insert into sub_types (id, type_id, value) values
(1, 3, 'something'),
(2, 2, 'blabla'),
(3, 3, 'something new'),
(4, 1, 'ok');
select type_id, json_agg(row_to_json(cj)) as json
from
sub_types st
cross join lateral
(select value, id) cj
group by type_id
;
type_id | json
---------+------------------------------------------------------------------
1 | [{"value":"ok","id":4}]
3 | [{"value":"something","id":1}, {"value":"something new","id":3}]
2 | [{"value":"blabla","id":2}]

I create types for all my json results and cast the rows to the type.
create table sub_types (
id int, type_id int, value text
);
create type sub_json_type as (value text, id integer);
insert into sub_types (id, type_id, value) values
(1, 3, 'something'),
(2, 2, 'blabla'),
(3, 3, 'something new'),
(4, 1, 'ok');
SELECT type_id, to_json(array_agg(row(value, id)::sub_json_type)) AS json FROM sub_types GROUP BY type_id;
type_id | json
---------+-----------------------------------------------------------------
1 | [{"value":"ok","id":4}]
2 | [{"value":"blabla","id":2}]
3 | [{"value":"something","id":1},{"value":"something new","id":3}]
(3 rows)

Related

SQLite query - filter name where each associated id is contained within a set of ids

I'm trying to work out a query that will find me all of the distinct Names whose LocationIDs are in a given set of ids. The catch is if any of the LocationIDs associated with a distinct Name are not in the set, then the Name should not be in the results.
Say I have the following table:
ID | LocationID | ... | Name
-----------------------------
1 | 1 | ... | A
2 | 1 | ... | B
3 | 2 | ... | B
I'm needing a query similar to
SELECT DISTINCT Name FROM table WHERE LocationID IN (1, 2);
The problem with the above is it's just checking if the LocationID is 1 OR 2, this would return the following:
A
B
But what I need it to return is
B
Since B is the only Name where both of its LocationIDs are in the set (1, 2)
You can try to write two subquery.
get count by each Name
get count by your condition.
then join them by count amount, which means your need to all match your condition count number.
Schema (SQLite v3.17)
CREATE TABLE T(
ID int,
LocationID int,
Name varchar(5)
);
INSERT INTO T VALUES (1, 1,'A');
INSERT INTO T VALUES (2, 1,'B');
INSERT INTO T VALUES (3, 2,'B');
Query #1
SELECT t2.Name
FROM
(
SELECT COUNT(DISTINCT LocationID) cnt
FROM T
WHERE LocationID IN (1, 2)
) t1
JOIN
(
SELECT COUNT(DISTINCT LocationID) cnt,Name
FROM T
WHERE LocationID IN (1, 2)
GROUP BY Name
) t2 on t1.cnt = t2.cnt;
| Name |
| ---- |
| B |
View on DB Fiddle
You can just use aggregation. Assuming no duplicates in your table:
SELECT Name
FROM table
WHERE LocationID IN (1, 2)
GROUP BY Name
HAVING COUNT(*) = 2;
If Name/LocationID pairs can be duplicated, use HAVING COUNT(DISTINCT LocationID) = 2.

Select objects from table where ids in set and sort result the same way as those ids are sorted

Let's assume I have a table table_data with serial id and text name.
select * from table_data where id in (3, 1, 5, 6, 2);
Result
id | name
6 | name6
5 | name5
1 | name1
3 | name3
2 | name2
But I wanted the result to be sorted as these ids.
id | name
3 | name3
1 | name1
5 | name5
6 | name6
2 | name2
These ids can be anything, they are retrieved dynamically before this query.
I would appreciate your help and advice.
Should work with a CASE in the order by:
SELECT *
FROM table_data
ORDER BY case id when 3 then 1
when 1 then 2
when 5 then 3
when 6 then 4
when 2 then 5
end
CREATE TABLE table_data (id INTEGER NOT NULL PRIMARY KEY , ztext text);
INSERT INTO table_data(id, ztext) VALUES
(1, 'One'), (2, 'Two'), (3, 'Three'), (4, 'Four'), (5, 'Five'), (6, 'Six');
WITH vals(val) AS (
VALUES (3), ( 1), ( 5), ( 6), ( 2)
)
, list(id,rnk) AS (
SELECT val
, row_number() OVER () AS rnk
FROM vals
)
SELECT t.id,t.ztext
FROM table_data t
JOIN list l ON l.id = t.id
ORDER BY l.rnk
;
Note: this relies on the ordering of items within a VALUES() set, which is probably not guaranteed. A better solution would be to use a set of pairs.

Count Based on Columns in SQL Server

I have 3 tables:
SELECT id, letter
FROM As
+--------+--------+
| id | letter |
+--------+--------+
| 1 | A |
| 2 | B |
+--------+--------+
SELECT id, letter
FROM Xs
+--------+------------+
| id | letter |
+--------+------------+
| 1 | X |
| 2 | Y |
| 3 | Z |
+--------+------------+
SELECT id, As_id, Xs_id
FROM A_X
+--------+-------+-------+
| id | As_id | Xs_id |
+--------+-------+-------+
| 9 | 1 | 1 |
| 10 | 1 | 2 |
| 11 | 2 | 3 |
| 12 | 1 | 2 |
| 13 | 2 | 3 |
| 14 | 1 | 1 |
+--------+-------+-------+
I can count all As and Bs with group by. But I want to count As and Bs based on X,Y and Z. What I want to get is below:
+-------+
| X,Y,Z |
+-------+
| 2,2,0 |
| 0,0,2 |
+-------+
X,Y,Z
A 2,2,0
B 0,0,2
What is the best way to do this at MSSQL? Is it an efficent way to use foreach for example?
edit: It is not a duplicate because I just wanted to know the efficent way not any way.
For what you're trying to do without knowing what is inefficient with your current code (because none was provided), a Pivot is best. There are a million resources online and here in the stack overflow Q/A forums to find what you need. This is probably the simplest explanation of a Pivot which I frequently need to remind myself of the complicated syntax of a pivot.
To specifically answer your question, this is the code that shows how the link above applies to your question
First Tables needed to be created
DECLARE #AS AS TABLE (ID INT, LETTER VARCHAR(1))
DECLARE #XS AS TABLE (ID INT, LETTER VARCHAR(1))
DECLARE #XA AS TABLE (ID INT, AsID INT, XsID INT)
Values were added to the tables
INSERT INTO #AS (ID, Letter)
SELECT 1,'A'
UNION
SELECT 2,'B'
INSERT INTO #XS (ID, Letter)
SELECT 1,'X'
UNION
SELECT 2,'Y'
UNION
SELECT 3,'Z'
INSERT INTO #XA (ID, ASID, XSID)
SELECT 9,1,1
UNION
SELECT 10,1,2
UNION
SELECT 11,2,3
UNION
SELECT 12,1,2
UNION
SELECT 13,2,3
UNION
SELECT 14,1,1
Then the query which does the pivot is constructed:
SELECT LetterA, [X],[Y],[Z]
FROM (SELECT A.LETTER AS LetterA
,B.LETTER AS LetterX
,C.ID
FROM #XA C
JOIN #AS A
ON A.ID = C.ASID
JOIN #XS B
ON B.ID = C.XSID
) Src
PIVOT (COUNT(ID)
FOR LetterX IN ([X],[Y],[Z])
) AS PVT
When executed, your results are as follows:
Letter X Y Z
A 2 2 0
B 0 0 2
As i said in comment ... just join and do simple pivot
if object_id('tempdb..#AAs') is not null drop table #AAs
create table #AAs(id int, letter nvarchar(5))
if object_id('tempdb..#XXs') is not null drop table #XXs
create table #XXs(id int, letter nvarchar(5))
if object_id('tempdb..#A_X') is not null drop table #A_X
create table #A_X(id int, AAs int, XXs int)
insert into #AAs (id, letter) values (1, 'A'), (2, 'B')
insert into #XXs (id, letter) values (1, 'X'), (2, 'Y'), (3, 'Z')
insert into #A_X (id, AAs, XXs)
values (9, 1, 1),
(10, 1, 2),
(11, 2, 3),
(12, 1, 2),
(13, 2, 3),
(14, 1, 1)
select LetterA,
ISNULL([X], 0) [X],
ISNULL([Y], 0) [Y],
ISNULL([Z], 0) [Z]
from (
select distinct a.letter [LetterA], x.letter [LetterX],
count(*) over (partition by a.letter, x.letter order by a.letter) [Counted]
from #A_X ax
join #AAs A on ax.AAs = A.ID
join #XXs X on ax.XXs = X.ID
)src
PIVOT
(
MAX ([Counted]) for LetterX in ([X], [Y], [Z])
) piv
You get result as you asked for
LetterA X Y Z
A 2 2 0
B 0 0 2

Get id of max value in group

I have a table and i would like to gather the id of the items from each group with the max value on a column but i have a problem.
SELECT group_id, MAX(time)
FROM mytable
GROUP BY group_id
This way i get the correct rows but i need the id:
SELECT id,group_id,MAX(time)
FROM mytable
GROUP BY id,group_id
This way i got all the rows. How could i achieve to get the ID of max value row for time from each group?
Sample Data
id = 1, group_id = 1, time = 2014.01.03
id = 2, group_id = 1, time = 2014.01.04
id = 3, group_id = 2, time = 2014.01.04
id = 4, group_id = 2, time = 2014.01.02
id = 5, group_id = 3, time = 2014.01.01
and from that i should get id: 2,3,5
Thanks!
Use your working query as a sub-query, like this:
SELECT `id`
FROM `mytable`
WHERE (`group_id`, `time`) IN (
SELECT `group_id`, MAX(`time`) as `time`
FROM `mytable`
GROUP BY `group_id`
)
Have a look at the below demo
DROP TABLE IF EXISTS mytable;
CREATE TABLE mytable(id INT , group_id INT , time_st DATE);
INSERT INTO mytable VALUES(1, 1, '2014-01-03'),(2, 1, '2014-01-04'),(3, 2, '2014-01-04'),(4, 2, '2014-01-02'),(5, 3, '2014-01-01');
/** Check all data **/
SELECT * FROM mytable;
+------+----------+------------+
| id | group_id | time_st |
+------+----------+------------+
| 1 | 1 | 2014-01-03 |
| 2 | 1 | 2014-01-04 |
| 3 | 2 | 2014-01-04 |
| 4 | 2 | 2014-01-02 |
| 5 | 3 | 2014-01-01 |
+------+----------+------------+
/** Query for Actual output**/
SELECT
id
FROM
mytable
JOIN
(
SELECT group_id, MAX(time_st) as max_time
FROM mytable GROUP BY group_id
) max_time_table
ON mytable.group_id = max_time_table.group_id AND mytable.time_st = max_time_table.max_time;
+------+
| id |
+------+
| 2 |
| 3 |
| 5 |
+------+
When multiple groups may contain the same value, you could use
SELECT subq.id
FROM (SELECT id,
value,
MAX(time) OVER (PARTITION BY group_id) as max_time
FROM mytable) as subq
WHERE subq.time = subq.max_time
The subquery here generates a new column (max_time) that contains the maximum time per group. We can then filter on time and max_time being identical. Note that this still returns multiple rows per group if the maximum value occurs multiple time within the same group.
Full example:
CREATE TABLE test (
id INT,
group_id INT,
value INT
);
INSERT INTO test (id, group_id, value) VALUES (1, 1, 100);
INSERT INTO test (id, group_id, value) VALUES (2, 1, 200);
INSERT INTO test (id, group_id, value) VALUES (3, 1, 300);
INSERT INTO test (id, group_id, value) VALUES (4, 2, 100);
INSERT INTO test (id, group_id, value) VALUES (5, 2, 300);
INSERT INTO test (id, group_id, value) VALUES (6, 2, 200);
INSERT INTO test (id, group_id, value) VALUES (7, 3, 300);
INSERT INTO test (id, group_id, value) VALUES (8, 3, 200);
INSERT INTO test (id, group_id, value) VALUES (9, 3, 100);
select * from test;
id | group_id | value
----+----------+-------
1 | 1 | 100
2 | 1 | 200
3 | 1 | 300
4 | 2 | 100
5 | 2 | 300
6 | 2 | 200
7 | 3 | 300
8 | 3 | 200
9 | 3 | 100
(9 rows)
SELECT subq.id
FROM (SELECT id,
value,
MAX(value) OVER (partition by group_id) as max_value
FROM test) as subq
WHERE subq.value = subq.max_value;
id
----
3
5
7
(3 rows)

Oracle SQL recursive query to find parent of level one

suppose i have a table service :
Name | ID | PARENT_ID | LEVEL |
-------------------------------------------
s1 | 1 | null | 0 |
s2 | 2 | 1 | 1 |
s3 | 3 | 1 | 2 |
s4 | 4 | 2 | 2 |
s5 | 5 | 3 | 3 |
s6 | 6 | 4 | 3 |
and i want to get the parent of level 1 for s6(id=6) which should return s2 , is there a way to make a recursive query until a level is reached ?
You can go UP the tree instead of going down - from leaf (id = 6) to root (which in this reverse case itself would be a leaf, connect_by_isleaf = 1), and take a "parent" of that leaf using prior operator.
upd: Misunderstood your requirement about LEVEL (in Oracle hierarchical queries it is a dynamic pseudocolumn specifying hierarchical depth of a row). If you want to limit your result set to rows with a specific value of your custom pre-populated LEVEL column - you can just add it to where condition.
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE t
("NAME" varchar2(2), "ID" int, "PARENT_ID" int, "LVL" int)
;
INSERT ALL
INTO t ("NAME", "ID", "PARENT_ID", "LVL")
VALUES ('s1', 1, NULL, 0)
INTO t ("NAME", "ID", "PARENT_ID", "LVL")
VALUES ('s2', 2, 1, 1)
INTO t ("NAME", "ID", "PARENT_ID", "LVL")
VALUES ('s3', 3, 1, 2)
INTO t ("NAME", "ID", "PARENT_ID", "LVL")
VALUES ('s4', 4, 2, 2)
INTO t ("NAME", "ID", "PARENT_ID", "LVL")
VALUES ('s5', 5, 3, 3)
INTO t ("NAME", "ID", "PARENT_ID", "LVL")
VALUES ('s6', 6, 4, 3)
SELECT * FROM dual
;
Query 1:
select id as id, name as name from t
where lvl = 1
connect by id = prior parent_id
start with id = 6
Results:
| ID | NAME |
|----|------|
| 2 | s2 |
This is possible with a hierarchical query:
create table tq84_h (
id number,
parent_id number,
level_ number
);
insert into tq84_h values (1, null, 0);
insert into tq84_h values (2, 1 , 1);
insert into tq84_h values (3, 1 , 2);
insert into tq84_h values (4, 2 , 2);
insert into tq84_h values (5, 3 , 3);
insert into tq84_h values (6, 4 , 3);
select
parent_id
from
tq84_h
where
level_ = 2
start with
id = 6
connect by
prior parent_id = id and
level_>1
;