SQL If condition - sql

I have a table called Train with this sample data:
train_id strt_stn_id end_stn_id direction
---------------------------------------------
1 1 10 D
2 1 21 D
3 10 1 U
4 1 5 D
5 1 15 D
and a Station table with this data:
stn_id stn_name
---------------------
1 Churchgate
2 Marine Lines
3 Charni Road
4 Grant Road
5 Mumbai Central
6 Mahalaxmi
7 Lower Parel
8 Elphinstone Road
9 Dadar
10 Matunga Road
11 Mahim
12 Bandra
13 Khar Road
14 Santacruz
15 Vile Parle
I want station name depending on the condition. If strt_stn_id > end_stn_id, I want station name of end_stn_id else station name of strt_stn_id.
So how to use if condition in a SQL query?

Try this:
SELECT T.*, S.stn_name
FROM Train T
INNER JOIN Station S
ON CASE WHEN T.strt_stn_id > T.end_stn_id
THEN T.end_stn_id ELSE T.strt_stn_id END = S.stn_id

use case
http://www.tizag.com/sqlTutorial/sqlcase.php
gives an example of how to use
'station' = CASE
WHEN (strt_stn_id > end_stn_id) THEN (end_stn_id)
ELSE (strt_stn_id)
END

Although you can definitely put the logic in the on clause, I much prefer to do two separate joins and put the logic in the select clause:
SELECT T.*,
(case when t.strt_stn_id > t.end_stn_id then starts.stn_name
else ends.stn_name
end) as TheName
FROM Train T INNER JOIN
Station Starts
ON T.strt_stn_id = Starts.stn_id inner join
Station ends
on T.end_stn_id = ends.stn_id
In general, a case statement or function all in an on clause prevents the engine from using indexes. If the tables have reasonable indexes (such as on their primary keys), this version will work better. As an aside, I also think having the logic in the select clause is more understandable, but I recognize that others might disagree.

Related

SQL COUNT with condition and without - using JOIN

My goal is something like following table:
Key | Count since date X | Count total
1 | 4 | 28
With two simple selects I could gain this values: (the key of the table consists of 3 columns [t$ncmp, t$trav, t$seqn])
1. SELECT COUNT(*) FROM db.table WHERE t$date >= sysdate-2 GROUP BY t$ncmp, t$trav, t$seqn
2. SELECT COUNT(*) FROM db.table GROUP BY t$ncmp, t$trav, t$seqn
How can I join these statements?
What I tried:
SELECT n.t$trav, COUNT(n.t$trav), m.total FROM db.table n
LEFT JOIN (SELECT t$ncmp, t$trav, t$seqn, COUNT(*) as total FROM db.table
GROUP BY t$ncmp, t$trav, t$seqn) m
ON (n.t$ncmp = m.t$ncmp AND n.t$trav = m.t$trav AND n.t$seqn = m.t$seqn)
WHERE n.t$date >= sysdate-2
GROUP BY n.t$ncmp, n.t$trav, n.t$seqn
I tried different variantes, but always got errors like 'group by is missing' or 'unknown qualifier'.
Now this at least executes, but total is always 2.
T$TRAV COUNT(N.T$TRAV) TOTAL
4 2 2
29 3 2
51 1 2
62 2 2
16 1 2
....
If it matter, I will run this as an OPENQUERY from MSSQLSERVER to Oracle-DB.
I'd try
GROUP BY n.t$trav, m.total
You typically GROUP BY the same columns as you SELECT - except those who are arguments to set functions.
My goal is something like following table:
If so, you seem to want conditional aggregation:
select key, count(*) as total,
sum(case when datecol >= date 'xxxx-xx-xx' then 1 else 0 end) as total_since_x
from t
group by key;
I'm not sure how this relates to your sample queries. I simply don't see the relationship between that code and your question.

Loop through rows and match values in SQL

appreciate any help with my problem! I have an org chart of all employees and then columns for their supervisors. I am trying to find the first in the org structure supervisor for each employee that has 3+ years' experience. So if supervisor 1 has only 1 year, I will need to move to the next column with super visor 2 and see if they have more experience. At the end, I would like to return a column of supervisors' ids [experienced_supervisor column]
Table: org_chart
id | experience | supervisor_id_1| supervisor_id_2 | experienced_supervisor
A | 2 | X | C | X
C | 5 | V | D | D
V | 1 | M | X | M
X | 3
D | 8
M | 11
I am new to SQL and not even sure if this is the best approach. But here is my thinking: I will use CASE to look though every row (employee) and compare their supervisor's experience.
SELECT CASE
WHEN experience >=3 THEN supervisor_id_1
ELSE
CASE WHEN experience >=3 THEN supervisor_id_2
ELSE 'not found'
END AS experienced_supervisor
FROM org_chart
Questions:
Is this the best way to tackle the problem?
Can I look up the value [experience years] of supervisors by matching supervisor_id_1, supervisor_id_2 to id? Or do I need to create a new column supervisor_id_1_experience and fill the years of experience by doing the join?
I am using Redshift.
You only need one case expression, but a lot of joins or subqueries. Perhaps
SELECT (CASE WHEN (SELECT oc2.experience >=3 FROM org_chart oc2 WHERE oc2.id = supervisor_id_1) >= 3
THEN supervisor_id_1
WHEN (SELECT oc2.experience >=3 FROM org_chart oc2 WHERE oc2.id = supervisor_id_2) >= 3
THEN supervisor_id_2
. . .
END) AS experienced_supervisor
FROM org_chart oc
After lots of trial and errors here is the result that worked for my problem. I am using Redshift in this case.
-- Use common table expression to find job level for each supervisor from reporting levels 8 to 2
WITH cte1 AS
(
SELECT B.id as employee
,B.experience as employee_experience
,B.supervisor_id_1 as manager_1
,A.experience as supervisor_1_experience
FROM org_chart
INNER JOIN org_chart B ON B.supervisor_id_1 = A.id
),
cte2 AS
(
SELECT B.id as employee2
,B.experience as employee_experience
,B.supervisor_id_2 as manager_2
,A.experience as supervisor_2_experience
FROM org_chart
INNER JOIN org_chart B ON B.supervisor_id_2 = A.id
),
........-- Write as many statements as I have columns with reporting levels
-- Join all tables above
cte3 AS
(
SELECT employee
,employee_experience
,manager_1
,supervisor_1_experience
,manager_2
,supervisor_2_experience
FROM cte1
JOIN cte2 ON cte2.employee2 = cte1.employee
....... -- Write as many statements as I have columns with reporting levels
)
-- Run through every row and evaluate if each supervisor has more than 3 years of experience
SELECT *
,CASE
WHEN cte3.supervisor_1_experience >= 3 THEN cte3.manager_1
WHEN cte3.supervisor_1_experience < 3
AND cte3.supervisor_2_experience >=3
THEN cte3.manager_2
........ -- Write as many statements as I have columns with reporting levels
END experienced_supervisor
FROM cte3

Count different groups in the same query

Imagine I have a table like this:
# | A | B | MoreFieldsHere
1 1 1
2 1 3
3 1 5
4 2 6
5 2 7
6 3 9
B is associated to A in an 1:n relationship. The table could've been created with a join for example.
I want to get both the total count and the count of different A.
I know I can use a query like this:
SELECT v1.cnt AS total, v2.cnt AS num_of_A
FROM
(
SELECT COUNT(*) AS cnt
FROM SomeComplicatedQuery
WHERE 1=1
-- AND SomeComplicatedCondition
) v1,
(
SELECT COUNT(A) AS cnt
FROM SomeComplicatedQuery
WHERE 1=1
-- AND SomeComplicatedCondition
GROUP BY A
) v2
However SomeComplicatedQuery would be a complicated and slow query and SomeComplicatedCondition would be the same in both cases. And I want to avoid calling it unnessesarily. Aside from that if the query changes, you need to make sure to change it in the other place too, making it prone to error and creating (probably unnessesary) work.
Is there a way to do this more efficiently?
Are you looking for this?
SELECT COUNT(*) AS total, COUNT(DISTINCT A) AS num_of_A
FROM (. . . ) q

Access SQL query to mailmerge

How can I transform this table from this
id name
1 sam
2 nick
3 ali
4 farah
5 josef
6 fadi
to
id1 name1 id2 name2 id3 name3 id4 name4
1 sam 2 nick 3 ali 4 farah
5 josef 6 fadi
the reason i need this is i have a database and i need to do a mail merge using word and I want to print every 4 rows on one page, MS word can only print one row per page, so using an SQL query I want one row to represent 4 rows
thanks in advance
Ali
You don't need to create a query for this in Access. Word has a merge field called <<Next Record>> which forces moving to the next record. If you look at how label documents are created using the Mail Merge Wizard, you'll see that's how it's done.
Updated - Doing this in SQL
The columns in simple SELECT statements are derived from the columns from the underlying table/query (or from expressions). If you want to define columns based on the data, you need to use a crosstab query.
First create a query with a running count for each person (say your table is called People), and calculate the row and column position from the running count:
SELECT People.id, Count(*)-1 AS RunningCount, int(RunningCount/4) AS RowNumber, RunningCount Mod 4 AS ColumnNumber
FROM People
LEFT JOIN People AS People_1 ON People.id >= People_1.id
GROUP BY People.id;
(You won't be able to view this in the Query Designer, because the JOIN isn't comparing with = but with >=.)
This query returns the following results:
id Rank RowNumber ColumnNumber
1 0 0 0
2 1 0 1
3 2 0 2
4 3 0 3
5 4 1 0
6 5 1 1
Assuming this query is saved as Positions, the following query will return the results:
TRANSFORM First(Item) AS FirstOfItem
SELECT RowNumber
FROM (
SELECT ID AS Item, RowNumber, "id" &( ColumnNumber + 1) AS ColumnHeading
FROM Positions
UNION ALL SELECT Name, RowNumber, "name" & (ColumnNumber +1)
FROM Positions
INNER JOIN People ON Positions.id = People.id
) AS AllValues
GROUP BY AllValues.RowNumber
PIVOT AllValues.ColumnHeading In ("id1","name1","id2","name2","id3","name3","id4","name4");
The UNION is there so each record in the People table will have two columns - one with the id, and one with the name.
The PIVOT clause forces the columns to the specified order, and not in alphabetical order (e.g. id1, id2 ... name1, name2...)

Conditionally append a character in select statement

Functionality I'm trying to add to my DB2 stored procedure:
Select a MIN() date from a joined table column.
IF there was more than one row in this joined table, append a " * " to the date.
Thanks, any help or guidance is much appreciated.
It's not clear which flavor of DB2 is needed nor if any suggestion worked. This works on DB2 for i:
SELECT
T1.joinCol1,
max( T2.somedateColumn ),
count( t2.somedateColumn ),
char(max( T2.somedateColumn )) concat case when count( T2.somedateColumn )>1 then '*' else '' end
FROM joinFile1 t1 join joinFile2 t2
on joinCol1 = joinCol2
GROUP BY T1.joinCol1
ORDER BY T1.joinCol1
The SQL is fairly generic, so it should translate to many environments and versions.
Substitute table and column names as needed. The COUNT() here actually counts rows from the JOIN rather than the number of times the specific date occurs. If a count of duplicate dates is needed, then some changes to this example are also needed.
Hope this helps
Say I have result coming as
1 Jeff 1
2 Jeff 333
3 Jeff 77
4 Jeff 1
5 Jeff 14
6 Bob 22
7 Bob 4
8 Bob 5
9 Bob 6
Here the value 1 is repeated twice(in 3 column)
So, this query gets the count as 2 along with the * concatenated along with it
SELECT A.USER_VAL,
DECODE(A.CNT, '1', A.CNT, '0', A.CNT, CONCAT(A.CNT, '*')) AS CNT
FROM (SELECT DISTINCT BT.USER_VAL, CAST(COUNT(*) AS VARCHAR2(2)) AS CNT
FROM SO_BUFFER_TABLE_8 BT
GROUP BY BT.USER_VAL) A