How to sort the query result by the number of specific column in ACCESS SQL? - sql

For example:
This is the original result
Alpha Beta
A 1
B 2
B 3
C 4
After Order by the number of Alpha, this is the result I want
Alpha Beta
B 2
B 3
A 1
C 4
I tried to use GroupBy and OrderBy, but ACCESS always ask me to include all columns.

Why is 'B' placed before 'A' ? I don't understand this order..
Any way, doesn't seem like you need a group by, not from your data sample, but for your desired result you can use CASE EXPRESSION :
SELECT t.alpha,t.beta FROM YourTable t
ORDER BY CASE WHEN t.alpha = 'B' THEN 1 ELSE 0 END DESC,
t.aplha,
t.beta
EDIT: Use this query:
SELECT t.alpha,t.beta FROM YourTable t
INNER JOIN(SELECT s.alpha,count(*) as cnt
FROM YourTable s
GROUP BY s.alpha) t2
ON(t.aplha = t2.alpha)
ORDER BY t2.cnt,t.alpha,t.beta

The query counts number of rows for every distinct Alpha and sorts. General Sql, tweak for ACCESS if needed.
SELECT t1.alpha,t1.beta
FROM t t1
JOIN (
SELECT t2.alpha, count(t2.*) AS n FROM t t2 GROUP BY t2.alpha
) t3 ON t3.alpha = t1.alpha
ORDER BY t3.n, t1.alpha, t1.beta

Related

SQL aggregate and filter functions

Consider following table:
Number | Value
1 a
1 b
1 a
2 a
2 a
3 c
4 a
5 d
5 a
I want to choose every row, where the value for one number is the same, so my result should be:
Number | Value
2 a
3 c
4 a
I manage to get the right numbers by using nested
SQL-Statements like below. I am wondering if there is a simpler solution for my problem.
SELECT
a.n,
COUNT(n)
FROM
(
SELECT number n , value k
FROM testtable
GROUP BY number, value
) a
GROUP BY n
HAVING COUNT(n) = 1
You can try this
SELECT NUMBER,MAX(VALUE) AS VALUE FROM TESTTABLE
GROUP BY NUMBER
HAVING MAX(VALUE)=MIN(VALUE)
You can try also this:
SELECT DISTINCT t.number, t.value
FROM testtable t
LEFT JOIN testtable t_other
ON t.number = t_other.number AND t.value <> t_other.value
WHERE t_other.number IS NULL
Another alternative using exists.
select distinct num, val from testtable a
where not exists (
select 1 from testtable b
where a.num = b.num
and a.val <> b.val
)
http://sqlfiddle.com/#!9/dd080dd/5

SQL fill gaps with hold

I've encountered a problem I cannot solve with my knowledge and I haven't found any solutions I understood good enough to solve my problem.
So here is what I try to achieve.
I have a database with the following structure:
node_id, source_time, value
1 , 10:13:15 , 1
2 , 10:13:15 , 1
2 , 10:13:16 , 2
1 , 10:13:19 , 2
1 , 10:13:25 , 3
2 , 10:13:28 , 3
I want to have a sql query to get the following output
time , value1, value2
10:13:15, 1 , 1
10:13:16, 1 , 2
10:13:19, 2 , 2
10:13:25, 3 , 2
10:13:28, 3 , 3
You see, the times are all times that occur from both nodes.
But the values have to be filled in the gaps since node1 has no value for the time :16 and :28.
I got it to the point where I get the 2 columns from one table. That was not the hard part.
SELECT T1.[value], T2.[value]
FROM [db1].[t_value_history] T1, [db1].[t_value_history] T2
WHERE ( T1.node_id = 1 AND T2.node_id = 2)
But the result doesn't look like the way I want it to be.
I found something with COALESCE and another table which holds the previous value. But that looked quiet complicated for such a easy thing.
I guess there is an easy sql solution but I haven't had much time to get into the materia.
I would be happy to get any idea which function to use.
Thanks so far.
Edit: Changed the database, made a mistake on the last line.
Edit2: I am using SQL Server. Sorry for not clarifying this. Also the values are not neccessarily increasing. I just used increasing numbers in this example here.
This works in SQL Server. If you are certain that there is a value for both nodes for the minimum time then you could change the OUTER APPLY to a CROSS APPLY, which would perform better.
WITH times
AS ( SELECT DISTINCT
source_time
FROM dbo.t_value_history
)
SELECT t.source_time ,
n1.value ,
n2.value
FROM times AS t
OUTER APPLY ( SELECT TOP 1
h.value
FROM dbo.t_value_history AS h
WHERE h.node_id = 1
AND h.source_time <= t.source_time
ORDER BY h.source_time DESC
) AS n1
OUTER APPLY ( SELECT TOP 1
h.value
FROM dbo.t_value_history AS h
WHERE h.node_id = 2
AND h.source_time <= t.source_time
ORDER BY h.source_time DESC
) AS n2;
You could use conditional aggregation to get the right set of rows:
select vh.source_time,
max(case when vh.node_id = 1 then value end) as value_1,
max(case when vh.node_id = 2 then value end) as value_2
from db1.t_value_history vh
group by vh.source_time;
If you want to fill in the values, then the best solution is lag() with ignore nulls. Supported by ANSI, but not by SQL Server (which I'm guessing you are using). Your values appear to be increasing. If that is the case, you can use a cumulative max:
select vh.source_time,
max(max(case when vh.node_id = 1 then value end)) over (order by vh.source_time) as value_1,
max(max(case when vh.node_id = 2 then value end) over (order by vh.source_time) as value_2
from db1.t_value_history vh
group by vh.source_time;
In your data, value is increasing, so this works for the data in your example. If that is not the case, a more complex query is needed to fill in the gaps.
This will do it in SQL Server. It is not 'nice' though:
SELECT DISTINCT
T1.source_time,
CASE WHEN T1.node_id = 1 THEN T1.[value] ELSE ISNULL(T2.[value], T3.[value]) END,
CASE WHEN T1.node_id = 1 THEN ISNULL(T2.[value], T3.[Value]) ELSE T1.[value] END
FROM
[db1].[t_value_history] T1
LEFT OUTER JOIN [db1].[t_value_history] T2 ON T2.source_time = T1.source_time
AND T2.node_id <> T1.node_id -- This join looks for a value for the other node at the same time.
LEFT OUTER JOIN [db1].[t_value_history] T3 ON T3.source_time < T1.source_time
AND T3.node_id <> T1.node_id -- If the previous join is empty, this looks for values for the other node at previous times
LEFT OUTER JOIN [db1].[t_value_history] T4 ON T4.source_time > T3.source_time
AND T4.source_time < T1.source_time
AND T4.node_id <> T1.node_id -- This join makes sure there aren't any more recent values
WHERE
T4.node_id IS NULL

SQL: Difference between rows

I am trying to find the difference between two numbers corresponding to the same ID, but with two different conditions. For example,
Column A Column B Column C
1234 3 True
1234 5 False
5678 10 True
5678 15 False
So basically i want to find the difference in Column B when column A is the same but Column C is different.
If we can assume
2 rows for a given value in ColumnA
expected result is only 1 row returned per unique colA value
columnC will never be null...
Column B will never match, and if they do, you don't want the record returned.
We can use a self join checking for matches on column A, no matches on column C and so that we don't get a row each for 1234 of 2 and -2.
SELECT Z.ColumnA, Z.B-Y.B
FROM TableName Z
INNER JOIN tableName Y
on Z.ColumnA = Y.ColumnA
and Z.ColumnC <> Y.ColumnC
and Z.ColumnB > Y.ColumnB
you could also do this with a window function using lead and look ahead to the next record. but I don't know if your RDBMS supports window functions.
select t1.ColumnA, t1.ColumnB-t2.ColumnB diff from
tab t1, tab t2
where t1.columnA = t2.columnA and t1.ColumnC='True' and t2.ColumnC='False'
If your table name is myTable, I believe you can join the table with itself and on Column A and then select a difference in Column B when Column C is not equal to itself. So something like this
SELECT X.ColumnB - Y.ColumnB as Diff
FROM myTable as X
inner join
myTable as Y
on X.ColumnA=Y.ColumnA
WHERE X.ColumnC <> Y.ColumnC
You could use a self join
select a.column_A, a,column_B, b.column_B, a.column_B . b.column_B
from my_table a
inner join my_column b where a.column_a = b.column_a and a.column_c <> b_column_c
If you RDBMS supports window functions
;with cte as (
Select *
,Change = ColumnB-Lag(ColumnB,1) over (Partition By ColumnA Order By (Select null))
,TrueFalse = Lag(ColumnC,1) over (Partition By ColumnA Order By (Select null))
From #YourTable
)
Select ColumnA
,Change
From cte
Where Change<>0
and TrueFalse<>ColumnC
Returns
ColumnA Change
1234 2
5678 5
This one subtracts the "true" rows from the "false" rows, while preserving all true rows.
SELECT true.columna, true.columnb, COALESCE(false.columnb, 0) - true.columnb AS difference
FROM
(SELECT *
FROM t
WHERE columnc = "true") true
LEFT JOIN
(SELECT *
FROM t
WHERE columnc = "false) false
ON true.columna = false.columna
The coalesce clause will account for cases in which there is no false row. Without it you will end up trying to subtract from NULL.
If you're using Oracle, take a look at User Defined aggregate functions. If you define a function called diff(), then you can use select column_A, diff(column_B) from my_table group by column_A, but remember that the implementation of diff needs special care as different ordering will generate different results.

Select rows having the same features than others

I've the following table with 3 columns: Id, FeatureName and Value:
Id FeatureName Value
-- ----------- -----
1 AAA 10
1 ABB 12
1 BBB 12
2 AAA 15
2 ABB 12
2 ACD 7
3 AAA 10
3 ABB 12
3 CCC 12
.............
Each Id has different features and each Feature has a value for that Id.
I need to write a query which gives me the Ids that have exactly the same features and values than a given one, but only taking into account those whose name starts with 'A'. For example, in the top table, I can use that query to search for all the Ids that have the same features. For example, features with values where Id=1 would result Id=3 with same features starting with 'A' and same values for these features.
I found a couple of different ways to do this, but all of them go very slow when the table has lots of rows (more than hundred of thousands)
The way I obtain the best performance is using the next query:
select a2.Id
from (select a.FeatureName, a.Value
from Table1 a
where a.Id = 1) a1,
(select a.Id, a.FeatureName, a.Value
from Table1 a
where a.FeatureName like 'A%') a2
where a1.FeatureName = a2.FeatureName
and a1.value = a2.value
group by a2.Id
having count(*) = 2
intersect
select a.Id
from Table1 a
where a.FeatureName like 'A%'
group by a.Id
having count(*)= 2
where #nFeatures is the number of features starting by 'A' in Id=1. I counted them before calling this query. I make the intersection to avoid results that have the same parameters than Id=1 but also some others whose name starts with 'A'.
I think that the slowest part is the second subquery:
select a.Id, a.FeaureName, a.Value
from MyTable a
where a.FeatureName = 'A%'
but I don't know how to make it faster. Maybe I will have to play with the indexes.
Any idea of how could I write a fast query for this purpose?
So you want all rows where the combination of FeatureName and Value is not unique? You can use EXISTS:
SELECT t.*
FROM dbo.Table1 t
WHERE t.FeatureName LIKE 'A%'
AND EXISTS(SELECT 1 FROM dbo.Table1 t2
WHERE t.Id <> t2.ID
AND t.FeatureName = t2.FeatureName
AND t.Value = t2.Value)
Demo
how could I write a fast query for this purpose?
If it's not fast enough create an index on FeatureName + Value.
I tried to eliminate the join with MyTable again to select the data for the ID's that have matching FeatureName and Value values. Here's the query:
with joined_set as
(
SELECT
mt1.*, mt2.id as mt2_id, mt2.featurename as mt2_FeatureName, mt2.value as mt2_value
from
(
select *
from mytable
where featurename like 'A%'
) mt1
left join
(
select *
from mytable
where featurename like 'A%'
) mt2
on mt2.id <> mt1.id and mt2.FeatureName = mt1.featurename and mt2.value = mt1.value
)
select distinct id
from joined_set
where id not in
(select id
from joined_set
group by id
having SUM(
CASE
WHEN mt2_id is null THEN 1
ELSE 0
END
) <> 0
);
Here is the SQL Fiddle demo. It has an extra condition in the inline view mt2, to perform this search only for id = 1.
I'm a little dense this morning, I'm not sure if you wanted just the ID's or...
Here's my take on it...
You could probably move the where FeatureName like 'A%' into the inner query to filter the data on the initial table scan.
with dupFeatures (FeatureName, Value, dupCount)
as
(
select FeatureName, Value, count(*) as dupCount from MyTable
group by FeatureName, Value
having count(*) > 1
)
select MyTable.Id, dupFeatures.FeatureName,dupFeatures.Value
from dupFeatures
join MyTable on (MyTable.FeatureName = dupFeatures.FeatureName and
MyTable.Value = dupFeatures.Value )
where dupFeatures.FeatureName like 'A%'
order by FeatureName, Value, Id
A general solution is
With Rows As (
select id
, FeatureName
, Value
, rows = Count(id) OVER (PARTITION BY id)
FROM test
WHERE FeatureName LIKE 'A%')
SELECT a.id aID, b.id bID
FROM Rows a
INNER JOIN Rows b ON a.id < b.id and a.FeatureName = b.FeatureName
and a.rows = b.rows
GROUP BY a.id, b.id
ORDER BY a.id, b.id
to limit the solution to a group just add a WHERE condition on the main query for a.ID. The CTE is needed to get the correct number of rows for each id
SQLFiddle demo, in the demo I changed little the test data to have a another couple of ID with only one of the FeatureName of 1 and 3

How to count specific values in a table

I've a column that have 15 distinct values. I'd like to count how many there are of a few of them,
I've come up with e.g.
select a,COUNT(IFNULL(b != 1,NULL)),COUNT(IFNULL(b != 2,NULL)) from
mytable group by a
select a,SUM(CASE WHEN a = 1 THEN 1 ELSE 0)),SUM(CASE WHEN a = 2 THEN 1 ELSE 0)) from
mytable group by a
What's the best way of doing this ? (note, I need to pivot those values to columns,
a simple select a,b,count(*) from mytable where b=1 or b=2 group by a,b; won't do.)
Of the two methods suggested in the question, I recommend the second:
select a,
SUM(CASE WHEN b = 1 THEN 1 ELSE 0) b1,
SUM(CASE WHEN b = 2 THEN 1 ELSE 0) b2
from mytable
group by a
- as it is both simpler and (I think) easier to understand, and therefore to maintain. I recommend including column aliases, as they make the output easier to understand.
First of all you misunderstood the IFNULL function (you probably wanted IF). See the documentation http://dev.mysql.com/doc/refman/5.0/en/control-flow-functions.html .
The second query you have in your question will give you what you want. But SUM(a=x) is more than sufficient. In MySQL true is equal to 1 and false is equal to 0.
have u try cross join?
select *
from (
select a, sum(...) as aSum
from mytable
where a...
group
by a
) as forA
cross join (
select b, sum(...) as bsum
from (
select *
from mytable
where b...
group
by b
)
) as forB;