Mimic the Excel LookUp function in SQL - sql

I want to mimic what a the Excel lookup function do but in SQL:
LOOKUP(recAge+1, 'reference'!refAge, 'reference'refVal')
I have 2 tables, records with a recAge column, and Gender which is the "reference" table and it has three columns: refAge, refVal and Gender.
What I want to do is get the refValue from the "reference" table where:
'refAge' == 'recAge'+1 and if that is not applicable then I should get the smallest value close to it.
Both have the same gender
Here is an example of what I have and what should be the final result:
recAge
Gender
1
F
1.5
M
2
F
2.5
M
The "reference" table:
refAge
refVal
Gender
1
13
F
1.5
17
F
2
12
F
2.5
11
F
1
10
M
1.5
15
M
2
14
M
2.5
19
M
I should be getting this as the result:
Gnder
recAge
refVal
F
1
12
M
1.5
19
F
2
11 >>> since 2+1= 3 and this does not exist in the Reference table
M
2.5
19 >>> same as the previous
I am stuck on how to join the two tables since there is no common key to apply the join on, I tried the following query but it only displays values of equal ages between the two tables.
WITH Ltable AS
(
SELECT
Gender, Age, refVal
FROM
Records
FULL JOIN
Reference ON Records.Age = Reference.Age
WHERE
(Records.Age+1 = Reference.Age)
OR (Records.Age + 1 > Reference.Age)
)
but it only shows me the (Records.Age+1 = Reference.Age) values and the reset of the ages are not matched with their closest smallest reference age to it.
I also tried the join on the gender but the same is happening.

You can use correlated sub-query for this and fetch the TOP 1 result for each row, based on your condition:
SELECT
rec.Gender
, rec.recAge
, (
SELECT TOP 1 refVal
FROM Reference ref
WHERE rec.Gender = ref.Gender
--ORDER BY ABS(rec.recAge + 1 - ref.refAge)
AND ref.refAge <= rec.recAge+1
ORDER BY ref.refAge DESC
) AS refVal
FROM Records rec
I am not sure I got your logic correctly, you might need to tweak, but you should get the idea.
DB<>Fiddle

Related

How can I retrieve previous and next rows by certain condition

I'm only aiming to retrieve rows before negative total values for each nickname and the same date.
Table :
enter image description here
I don't want to retrieve the orderid 8 and the orderid9 because the above rows for the same nickname and the same day contain negative total value. For the same reason, I don't want to retrieve the row with orderid 7. I don't want to retrieve the orderid 5 and the orderid 6 since they contain negative total value. I'm aiming to retrieve the orderid10 although the above rows for the same nickname contain negative value, because the date has changed.
Expected result:
enter image description here
I've tried to solve using with clauses and subqueries but I've failed.
welcome to Stack Overflow.
When you're asking a question like this it's really helpful to provide DDL and DML to properly explain your issue. Please don't use images for this, as they can't be easily copy & pasted. A good way to provide the demo data is:
DECLARE #table TABLE (RowID INT IDENTITY, Value INT, Name NVARCHAR(10))
INSERT INTO #table (Value, Name) VALUES
(1, 'E'),(2, 'D'),(3, 'C'),(4, 'B'),(5, 'A'),
(1, 'V'),(2, 'W'),(3, 'X'),(4, 'Y'),(5, 'Z'),
(1, 'M'),(2, 'N'),(3, 'O'),(4, 'P'),(5, 'Q')
On to your question.
It sounds like what you're looking for is the LAG and LEAD windowed functions. Windowed functions operate on a window which you define:
SELECT *, LAG(Name,1) OVER (PARTITION BY Value ORDER BY RowID) AS PreviousName, LEAD(Name,1) OVER (PARTITION BY Value ORDER BY RowID) AS NextName
FROM #table
These functions take the column name you want, and the number or rows to move (LAG moves backwards, LEAD moves forward). In the OVER we use PARTITION and ORDER BY to define the window. PARTITION basically groups things together and ORDER BY determines the order in that group. If the row moved to doesn't exist (there was no previous, or no next) NULL is returned instead.
RowID Value Name PreviousName NextName
------------------------------------------------
1 1 E NULL V
6 1 V E M
11 1 M V NULL
2 2 D NULL W
7 2 W D N
12 2 N W NULL
3 3 C NULL X
8 3 X C O
13 3 O X NULL
4 4 B NULL Y
9 4 Y B P
14 4 P Y NULL
5 5 A NULL Z
10 5 Z A Q
15 5 Q Z NULL
If you can provide some better example data I can probably answer the other points of your question too.

JOIN on aggregate function

I have a table showing production steps (PosID) for a production order (OrderID) and which machine (MachID) they will be run on; I’m trying to reduce the table to show one record for each order – the lowest position (field “PosID”) that is still open (field “Open” = Y); i.e. the next production step for the order.
Example data I have:
OrderID
PosID
MachID
Open
1
1
A
N
1
2
B
Y
1
3
C
Y
2
4
C
Y
2
5
D
Y
2
6
E
Y
Example result I want:
OrderID
PosID
MachID
1
2
B
2
4
C
I’ve tried two approaches, but I can’t seem to get either to work:
I don’t want to put “MachID” in the GROUP BY because that gives me all the records that are open, but I also don’t think there is an appropriate aggregate function for the “MachID” field to make this work.
SELECT “OrderID”, MIN(“PosID”), “MachID”
FROM Table T0
WHERE “Open” = ‘Y’
GROUP BY “OrderID”
With this approach, I keep getting error messages that T1.”PosID” (in the JOIN clause) is an invalid column. I’ve also tried T1.MIN(“PosID”) and MIN(T1.”PosID”).
SELECT T0.“OrderID”, T0.“PosID”, T0.“MachID”
FROM Table T0
JOIN
(SELECT “OrderID”, MIN(“PosID”)
FROM Table
WHERE “Open” = ‘Y’
GROUP BY “OrderID”) T1
ON T0.”OrderID” = T1.”OrderID”
AND T0.”PosID” = T1.”PosID”
Try this:
SELECT “OrderID”,“PosID”,“MachID” FROM (
SELECT
T0.“OrderID”,
T0.“PosID”,
T0.“MachID”,
ROW_NUMBER() OVER (PARTITION BY “OrderID” ORDER BY “PosID”) RNK
FROM Table T0
WHERE “Open” = ‘Y’
) AS A
WHERE RNK = 1
I've included the brackets when selecting columns as you've written it in the question above but in general it's not needed.
What it does is it first filters open OrderIDs and then numbers the OrderIDs from 1 to X which are ordered by PosID
OrderID
PosID
MachID
Open
RNK
1
2
B
Y
1
1
3
C
Y
2
2
4
C
Y
1
2
5
D
Y
2
2
6
E
Y
3
After it filters on the "rnk" column indicating the lowest PosID per OrderID. ROW_NUMBER() in the select clause is called a window function and there are many more which are quite useful.
P.S. Above solution should work for MSSQL

Finding a pair of row in SQL

I am very confused how to define the problem statement but Let's say below is table History i want to find those rows which have a pair.
Pair I will defined like column a and b will have same value and c should have False and d should be different for both row.
If I am using Java i would have set row 3, C column as true when i hit a pair or would have saved both row 1 and row 3 into different list. So that row 2 can be excluded. But i don't know how to do the same functionality in SQL.
Table - History
col a, b, c(Boolean ), d
1 bb F d
1 bb F d
1 bb F c
Query ? ----
Result - rows 1 and 3.
Assuming the table is called test:
SELECT
*
FROM
test
WHERE id IN (
SELECT
MIN(id)
FROM
test
WHERE
!c
AND a = b
AND d != a
GROUP BY a, d
)
We get the smallest id of every where matching your conditions. Furthermore we group the results by a, d which means we get only unique pairs of "a and d". Then we use this ids to select the rows we want.
Working example.
Update: without existing id
# add PK afterwards
ALTER TABLE test ADD COLUMN id INT PRIMARY KEY AUTO_INCREMENT FIRST;
Working example.
All the rows match the conditioin you specified. A "pair" happens when:
column a and b will have same value, and
c should have False, and
d should be different for both rows.
1 and 3 will match that as well as 2 and 3. Also, 3 and 1 will match as well as 3 and 2. There are four solutions.
You don't say which database, so I'll assume PostgreSQL. The query that can search using your criteria is:
select *
from t x
where exists (
select null from t y
where y.a = x.a
and y.b = x.b
and not y.c
and y.d <> x.d
);
Result:
a b c d
-- --- ------ -
1 bb false d
1 bb false d
1 bb false c
That is... the whole table.
See running example at DB Fiddle.

How to count values of a column of the same type in postgresql?

I have 3 tables and after doing some joinings I've come to the stage where I'm trying to count the number of times a value of the same type (1 & 2) occurs in a column (column id) but I'm not sure how to create the query. Help?
parent_id id
1 1
1 1
2 2
1 1
1 1
1 1
In the id table, number "1" occurred 5 times and "2" 1 time. How can I create the query that resulted in a new column (maybe 2 columns for each) to show that "1" occurs 5 times and "2" occurs 1 time? This is the queries from the 3 original tables that led me to the id table.
SELECT parent_id, pods.id
FROM visits
JOIN parents
ON parents.id = visits.parent_id
JOIN pods
ON parents.pod_id = pods.id
I'm trying to get two columns: column 1 with the total number of "1" which is 5 and column 2 with the total number of "2" which is 1.
Thank you in advance!
Are you just looking for group by?
SELECT po.id, COUNT(*)
FROM visits v JOIN
parents p
ON p.id = v.parent_id JOIN
pods po
ON p.pod_id = po.id
GROUP BY po.id;

Need to find out if all columns in a SQL Server table have the same value

I have the task to find out if all columns in a SQL Server table have exact the same value. The table content is created by a stored procedure and can vary in the number of columns. The first column is an ID, the second and the following columns must be compared if the all columns have exact the same value.
At the moment I do not have a clue how to achieve this.
The best solution would be to display only the rows, which have different values in one or multiple columns except the first column with ID.
Thank you so much for your help!!
--> Edit: The table looks this:
ID Instance1 Instance2 Instance3 Instance4 Instance5
=====================================================
A 1 1 1 1 1
B 1 1 0 1 1
C 55 55 55 55 55
D Driver Driver Driver Co-driver Driver
E 90 0 90 0 50
F On On On On On
The result should look like this, only the rows with one or multiple different column values should be display.
ID Instance1 Instance2 Instance3 Instance4 Instance5
=====================================================
B 1 1 0 1 1
D Driver Driver Driver Co-driver Driver
E 90 0 90 0 50
My table has more than 1000 rows and 40 columns
you can achieve this by using row_number()
Try the following code
With c as(
Select id
,field_1
,field_2
,field_3
,field_n
,row_number() over(partition by field_1,field_2,field_3,field_n order by id asc) as rn
From Table
)
Select *
From c
Where rn = 1
row_number with partition is going to show you if the field is repeated by assigning a number to a row based on field_1,field_2,field_3,field_n, for example if you have 2 rows with same field values the inner query is going to show you
rn field_1 field_2 field_3 field_n id
1 x y z a 5
2 x y z a 9
After that on the outer part of the query pick rn = 1 and you are going to obtain a query without repetitions based on fields.
Also if you want to delete repeated numbers from your table you can apply
With c as(
Select id
,field_1
,field_2
,field_3
,field_n
,row_number() over(partition by field_1,field_2,field_3,field_n order by id asc) as rn
From Table
)
delete
From c
Where rn > 1
The best solution would be to display only the rows, which have different values in one or multiple columns except the first column with ID.
You may be looking for a the following simple query, whose WHERE clause filters out rows where all fields have the same value (I assumed 5 fields - id not included).
SELECT *
FROM mytable t
WHERE NOT (
field1 = field2
AND field1 = field3
AND field1 = field4
AND field1 = field5
);