Unique aggregate function when singular value is guaranteed by the WHERE clause - sql

Given the following:
CREATE TABLE A (A1 INTEGER, A2 INTEGER, A3 INTEGER);
INSERT INTO A(A1, A2, A3) VALUES (1, 1, 1);
INSERT INTO A(A1, A2, A3) VALUES (2, 1, 1);
I want to select the maximum A1 given specific A2 and A3 values, and have those values (A2 and A3) also appear in the returned row (e.g. so that I may use them in a join since the SELECT below is meant for a sub-query).
It would seem logical to be able to do the following, given that A2 and A3 are hardcoded in the WHERE clause:
SELECT MAX(A1) AS A1, A2, A3 FROM A WHERE A2=1 AND A3=1
However, PostgreSQL (and I suspect other RDBMs as well) balks at that and requests an aggregate function for A2 and A3 even though their value is fixed. So instead, I either have to do a:
SELECT MAX(A1) AS A1, MAX(A2), MAX(A3) FROM A WHERE A2=1 AND A3=1
or a:
SELECT MAX(A1) AS A1, 1, 1 FROM A WHERE A2=1 AND A3=1
The first alternative I don't like cause I could have used MIN instead and it would still work, whereas the second alternative doubles the number of positional parameters to provide values for when used from a programming language interface. Ideally I would have wanted a UNIQUE aggregate function which would assert that all values are equal and return that single value, or even a RANDOM aggregate function which would return one value at random (since I know from the WHERE clause that they are all equal).
Is there an idiomatic way to write the above in PostgreSQL?

Even simpler, you only need ORDER BY / LIMIT 1
SELECT a1, a2, a3 -- add more columns as you please
FROM a
WHERE a2 = 1 AND a3 = 1
ORDER BY 1 DESC -- 1 is just a positional reference (syntax shorthand)
LIMIT 1;
LIMIT 1 is Postgres specific syntax.
The SQL standard would be:
...
FETCH FIRST 1 ROWS ONLY
My first answer with DISTINCT ON was for the more complex case where you'd want to retrieve the maximum a1 per various combinations of (a2,a3)
Aside: I am using lower case identifiers for a reason.

how about group by
select
a2
,a3
,MAX(a1) as maximumVal
from a
group by a2, a3

Does this work for you ?
select max(A1),A2,A3 from A GROUP BY A2,A3;
EDIT
select A1,A2,A3 from A where A1=(select max(A1) from A ) limit 1

A standard trick to obtain the maximal row without an aggregate function is to guantee the absense of a larger value by means of a NOT EXISTS subquery. (This does not work when there are ties, but neither would the subquery with the max) When needed, it would not be too difficult to add a tie-breaker condition.
Another solution would be a subquery with a window function row_number() or rank()
SELECT *
FROM a src
WHERE NOT EXISTS ( SELECT * FROM a nx
WHERE nx.a2 = src.a2
AND nx.a3 = src.a3
AND nx.a1 > src.a1
);

Related

How to write a query in sql which does "=COUNTIF(B2:B4,">"&B2)" of excel/sheets?

I am trying to do "=COUNTIF(B1:B3,">"&B1)" in sql, it counts the number of elements in a column, from B1 row to B3, which are greater then B1. We are doing this for every 3 rows.
Suppose we have B column
532.02
667.96
588.1
579.35
623.98
621.29
Now we i need to calculate the number of elements which are greater then elements B(i) for every three rows.
532.02 -> 2 (=COUNTIF(B1:B3,">"&B1) (Number of elements from B1 to B3 which are greater then B1)
667.96 -> 0 (=COUNTIF(B2:B3,">"&B2) (Number of elements from B2 to B3 which are greater then B2)
588.1 -> 0 (=COUNTIF(B3:B3,">"&B3)(Number of elements from B3 to B3 which are greater then B3)
579.35 -> 2 (=COUNTIF(B4:B6,">"&B4) (Number of elements from B4 to B6 which are greater then B4)
623.98 -> 0 (=COUNTIF(B5:B6,">"&B5) (Number of elements from B5 to B6 which are greater then B5)
621.29 -> 0 (=COUNTIF(B6:B6,">"&B6) (Number of elements from B6 to B6 which are greater then B6)
So in sheets/excel, we use this (=COUNTIF(B5:B6,">"&B5), but how write query in sql on the basis of this?
You could of use the CELL function with a new column that has the intervals of 3 (3,6,9,etc).
Then, you can build the formula to get the value of the interval column and proceed with the formula you have.
So, the formula to build would be like:
(=COUNTIF(B5:"B"&=CELL("contents", H33),">"&B5)
In this case, H33 has the interval.
First of all if you want to have different behaviour depending on the position of the record (the groups of 3) then you have to have some way of showing that position, SQL data isn't guaranteed to be in a specific order unless you specify. You would likely need to add a unique ID for each line that increments.
For example:
CREATE TABLE MY_TABLE
(
ROW_N NUMBER,
AMMOUNT NUMBER
);
INSERT INTO MY_TABLE VALUES (1,532.02);
INSERT INTO MY_TABLE VALUES (2,667.96);
INSERT INTO MY_TABLE VALUES (3,588.1);
INSERT INTO MY_TABLE VALUES (4,579.35);
INSERT INTO MY_TABLE VALUES (5,623.98);
INSERT INTO MY_TABLE VALUES (6,621.29);
Then you can use a modulo function to change behaviour depending on whether the line you are working with has a reference that is exactly divisible by 3, a remainder of one, or a remainder of 2.
In order to compare values between different records on the same table you would likely need to use a self join. In this instance you are sometimes doing this twice, so you need 2 self joins.
The way i implimented it was to use a case statement, which is fine for doing a couple of comparisons, but isnt really scalable.
I used unions at the end just to get the data back into the original format.
WITH calcs
AS (SELECT a.row_n AS rown_a,
a.ammount AS ammount_a,
0 AS gtr_a,
b.row_n AS rown_b,
b.ammount AS ammount_b,
CASE
WHEN b.ammount < a.ammount THEN 1
ELSE 0
END AS gtr_b,
c.row_n AS rown_c,
c.ammount AS ammount_c,
CASE
WHEN c.ammount > a.ammount
AND c.ammount < b.ammount THEN 1
WHEN c.ammount < a.ammount
AND c.ammount > b.ammount THEN 1
WHEN c.ammount < a.ammount
AND c.ammount < b.ammount THEN 2
ELSE 0
END AS gtr_c
FROM my_table a,
my_table b,
my_table c
WHERE MOD(a.row_n, 3) = 0
AND b.row_n = a.row_n - 1
AND c.row_n = a.row_n - 2)
SELECT *
FROM (SELECT rown_a,
ammount_a,
gtr_a
FROM calcs
UNION
SELECT rown_b,
ammount_b,
gtr_b
FROM calcs
UNION
SELECT rown_c,
ammount_c,
gtr_c
FROM calcs) x
ORDER BY 1
This query is a bit of a mess, but what you are trying to do in SQL is a bit weird IMHO
I just wanted to add something. Maybe this query can add some value to you, as to do operations with the following rows without the use of joins.
SELECT
ROW#,
B_column first#,
SUM(B_column) OVER (order by ROW# rows between 1 following and 1 following) second#,
SUM(B_column) OVER (order by ROW# rows between 2 following and 2 following) third#
FROM (
SELECT
row_number() over (order by (select NULL)) ROW#,
B_column
FROM [your_schema].[your_table]
) A
Thank you

Convert SQL Data in columns into an array

Imagine you have a simple table
Key c1 c2 c3
A id1 x y z
B id2 q r s
what I would like is a query that gives me the result as 2 arrays
so something like
Select
id1,
ARRAY_MAGIC_CREATOR(c1, c2, c3)
from Table
With the result being
id1, <x,y,z>
id2, <q,r,s>
Almost everything I have searched for end up converting rows to arrays or other similar sounding but very different requests.
Does something like this even exist in SQL?
Please note that the data type is NOT a string so we can't use string concat. They are all going to be treated as floats.
It is called ARRAY:
Select id1, ARRAY[c1, c2, c3] as c_array
from Table
This will also work :o)
select key, [c1, c2, c3] c
from `project.dataset.table`
Consider below generic option which does not require you to type all columns names or even to know them in advance - more BigQuery'ish way of doing business :o)
select key,
regexp_extract_all(
to_json_string((select as struct * except(key) from unnest([t]))),
r'"[^":,]+":([^":,]+)(?:,|})'
) c
from `project.dataset.table` t
If applied to sample data in your question - output is

How can I use a row value to dynamically select a column name in Oracle SQL 11g?

I have two tables, one with a single row for each "batch_number" and another with defect details for each batch. The first table has a "defect_of_interest" column which I would like to link to one of the columns in the second table. I am trying to write a query that would then pick the maximum value in that dynamically linked column for any "unit_number" in the "batch_number".
Here is the SQLFiddle with example data for each table: http://sqlfiddle.com/#!9/a1c27d
For example, the maximum value in the DEFECT_DETAILS.SCRATCHES column for BATCH_NUMBER = A1 is 12.
Here is my desired output:
BATCH_NUMBER DEFECT_OF_INTEREST MAXIMUM_DEFECT_COUNT
------------ ------------------ --------------------
A1 SCRATCHES 12
B3 BUMPS 4
C2 STAINS 9
I have tried using the PIVOT function, but I can't get it to work. Not sure if it works in cases like this. Any help would be much appreciated.
If the number of columns is fixed (it seems to be) you can use CASE to select the specific value according to the related table. Then aggregating is simple.
For example:
select
batch_number,
max(defect_of_interest) as defect_of_interest,
max(defect_count) as maximum_defect_count
from (
select
d.batch_number,
b.defect_of_interest,
case when b.defect_of_interest = 'SCRATCHES' then d.scratches
when b.defect_of_interest = 'BUMPS' then d.bumps
when b.defect_of_interest = 'STAINS' then d.stains
end as defect_count
from defect_details d
join batches b on b.batch_number = d.batch_number
) x
group by batch_number
order by batch_number;
See Oracle example in db<>fiddle.

Browse subcolumns, but discard some

I have a table (or view) in my PostgreSQL database and want to do the following:
Query the table and feed a function in my application subsequent n-tuples of rows from the query, but only those that satisfy some condition. I can do the n-tuple listing using a cursor, but I don't know how to do the condition checking on database level.
For example, the query returns:
3
2
4
2
0
1
4
6
2
And I want triples of even numbers. Here, they would be:
(2,4,2) (4,2,0) (4,6,2)
Obviously, I cannot discard the odd numbers from the query result. Instead using cursor, a query returning arrays in similar manner would also be acceptable solution, but I don't have any good idea how to use them to do this.
Of course, I could check it at application level, but I think it'd be cleaner to do it on database level. Is it possible?
With the window function lead() (as mentioned by #wildplasser):
SELECT *
FROM (
SELECT tbl_id, i AS i1
, lead(i) OVER (ORDER BY tbl_id) AS i2
, lead(i, 2) OVER (ORDER BY tbl_id) AS i3
FROM tbl
) sub
WHERE i1%2 = 0
AND i2%2 = 0
AND i3%2 = 0;
There is no natural order of rows - assuming you want to order by tbl_id in the example.
% .. modulo operator
SQL Fiddle.
You can also use an array aggregate for this instead of using lag:
SELECT
a[1] a1, a[2] a2, a[3] a3
FROM (
SELECT
array_agg(i) OVER (ORDER BY tbl_id ROWS BETWEEN 2 PRECEDING AND CURRENT ROW)
FROM
tbl
) x(a)
WHERE a[1] % 2 = 0 AND a[2] % 2 = 0 AND a[3] % 2 = 0;
No idea if this'll be better, worse, or the same as Erwin's answer, just putting it in for completeness.

how to get the comma separated values of the column stored in the Sql server

how to get the comma separated values stored in the Sql Db into a individual values
e.g in sql DB the column is stored with comma values as shown below,
EligibleGroup
A11,A12,A13
B11,B12,B13
I need to get
EligibleGroup
A11
A12
A13
B11
B12
...
I have written a query that will fetch me some list of employees with employee name and eligible group
XXX A11
YYY B11
ZZZ C11
I need to check that the employees(XXX,YYY,ZZZ) eligiblegroup falls within this
EligibleGroup
A11,A12,A13
B11,B12,B13
and return me only that rows.
use a "user defined function" like the one shown here (including source code) - it returns the splitted values as a "table" (one row per value) you can select from like
select txt_value from dbo.fn_ParseText2Table('A11,A12,A13')
returns
A11
A12
A13
You could use a subquery:
SELECT employee_name, eligible_group
FROM YourTable
WHERE eligible_group IN
(SELECT SPLIT(EligibleGroup)
FROM tblEligibleGroup
WHERE <some conditions here>)
I don't believe the "SPLIT" function exists in SQL Server so you'll have to either create a user defined function to handle that, or you could use the nifty workaround suggested here: How do I split a string so I can access item x?
I think you can do it this way,
select left('A11,A12,A13',3) + SUBSTRING('A11,A12,A13',charindex(',','A11,A12,A13'),10)
I think you may not have to split EligibleGroup. You can do another way by just:
select empId
from yourTempEmpTable t1, EligibleGroup t2
where t2.elibigle like '%'+t1.elibigle+'%'
I think it should work.
Assuming that EligibleGroup has a fixed length data, you can try using SUBSTRING As follows:
select substring(EligibleGroup,1,3) from #test union all
select substring(EligibleGroup,5,3) from #test union all
select substring(EligibleGroup,9,3) from #test
This will return:
A11
A12
A13
B11
B12
...
You can try it in Data Explorer
And If you need to check if an employee fall into which EligibleGroup try this:
Select EligibleGroup from test where eligibleGroup like '%A11'