Reordering output to predefined sequence - sql

I am trying to get output from a table sorted in a predefined sequence of 5 alphabet.
i.e. L > C > E > O > A
by using order by I cant get the desired result. I am using SQL server db.
Can any one please suggest me if I can define a sequence inside a query ?
SO that I get my result in L > C > E > O > A.
Thanks in Advance.

select * from your_table
order by case when some_column = 'L' then 1
when some_column = 'C' then 2
when some_column = 'E' then 3
when some_column = 'O' then 4
when some_column = 'A' then 5
end desc

If you want to use those sorting criteria for two or more queries then you can create a table for this:
CREATE TABLE dbo.CustomSort (
Value VARCHAR(10) PRIMARY KEY,
SortOrder INT NOT NULL
);
GO
INSERT INTO dbo.CustomSort (Value, SortOrder) VALUES ('L', 1);
INSERT INTO dbo.CustomSort (Value, SortOrder) VALUES ('C', 2);
INSERT INTO dbo.CustomSort (Value, SortOrder) VALUES ('E', 3);
INSERT INTO dbo.CustomSort (Value, SortOrder) VALUES ('O', 4);
INSERT INTO dbo.CustomSort (Value, SortOrder) VALUES ('A', 5);
GO
and then you can join the source table (x in this example) with dbo.CustomSort table thus:
SELECT x.Col1
FROM
(
SELECT 'E' UNION ALL
SELECT 'C' UNION ALL
SELECT 'O'
) x(Col1) INNER JOIN dbo.CustomSort cs ON x.Col1 = cs.Value
ORDER BY cs.SortOrder
/*
Col1
----
C
E
O
*/
I you update the dbo.CustomSort table then all queries will use the new sorting criteria.

Related

SQL Group By specific column with nullable

Let's say I have this data in my table A:
group_id
type
active
1
A
true
1
B
false
1
C
true
2
null
false
3
B
true
3
C
false
I want to create a query which return the A row if exists (without the type column), else return a row with active false.
For this specific table the result will be:
group_id
active
1
true
2
false
3
false
How can I do this ?
I'm assuming I have to use a GROUP BY but I can't find a way to do it.
Thank you
This is a classic row_number problem, generate a row number based on your ordering criteria, then select just the first row in each grouping.
declare #MyTable table (group_id int, [type] char(1), active bit);
insert into #MyTable (group_id, [type], active)
values
(1, 'A', 1),
(1, 'B', 0),
(1, 'C', 1),
(2, null, 0),
(3, 'B', 1),
(3, 'C', 0);
with cte as (
select *
, row_number() over (
partition by group_id
order by case when [type] = 'A' then 1 else 0 end desc, active asc
) rn
from #MyTable
)
select group_id, active
from cte
where rn = 1
order by group_id;
Returns:
group_id
active
1
1
2
0
3
0
Note: Providing the DDL+DML as I have shown makes it much easier for people to assist.
This should do it. We select all the distinct group_ids and then join our table back to that. There is an ISNULL function that will insert the 'false' when 'A' type records are not found.
DECLARE #tableA TABLE (
group_id int
, [type] nchar(1)
, active nvarchar(10)
);
INSERT INTO #tableA (group_id, [type], active)
VALUES
(1, 'A', 'true')
, (1,'B','false')
, (1,'C', 'false')
, (2, null, 'false')
, (3, 'B', 'true')
, (3, 'C', 'false')
;
SELECT
gid.group_id
, ISNULL(a.active,'false') as active
FROM (SELECT DISTINCT group_id FROM #tableA) as gid
LEFT OUTER JOIN #tableA as a
ON a.group_id = gid.group_id
AND a.type = 'A'

How to know the column name from a table based on the column values

I am working in Informix and I want to know if there is a simple way to know the tabname/colname by its possible column values.
For example:
table1
Register 1
==========
id 1
col1 3
col2 Y
Register 2
==========
id 2
col1 43
col2 X
Register 3
==========
id 2
col1 0
col2 Z
Register 4
==========
id 2
col1 23
col2 F
table2
Register 1
==========
id 1
col1 X
col2 Y
Register 2
==========
id 2
col1 X
col2 X
Register 3
==========
id 2
col1 Z
col2 Z
Register 4
==========
id 2
col1 X
col2 X
table3
Register 1
==========
id 1
col1 ASX
With this database, if I want to know the colnames and their related tabnames of the database that contain X, Y and Z (amoung other values).
It could be something like this:
select tabname, colname
where ('X','Y','Z') in colnamevalues --this has been invented by me
And this should return the following values:
table1.col2
table2.col1
table2.col2
--Note that the columns fetched contains also other values
--different from 'X', 'Y' and 'Z' but T didn't fix in this case
--the whole list of values, only some of them
I have queried for other Q&A but all of them look to use some functions of other databases such as Oracle or SQL Server and I don't understand them very well.
You can get all the tables that exist on a database by querying the systables:
SELECT tabname
FROM systables
WHERE tabtype = 'T' --get only tables
AND tabid > 99; --skip catalog tables
You can join it to the syscolumns table to get the columns:
SELECT t.tabname, c.colname
FROM systables t
INNER JOIN syscolumns c ON (c.tabid = t.tabid)
WHERE t.tabtype = 'T' AND t.tabid > 99;
And if you know the type of values you can even filter it. Example if you're looking for "strings":
SELECT t.tabname, c.colname
FROM systables t
INNER JOIN syscolumns c ON (c.tabid = t.tabid)
WHERE t.tabtype = 'T' AND t.tabid > 99
AND MOD(c.coltype,256) IN (
0, --CHAR
13, --VARCHAR
15, --NCHAR
16, --NVARCHAR
40, --LVARCHAR
43 --LVARCHAR
);
The next example works, but it really should be optimized and bullet proof, but can get you kick off.
When I have time I get another look at it and check what can be optimized and put some error handling.
Another way to do it is scripting, what OS are you running?
Schema creation:
CREATE TABLE tab1(
id INT,
col1 CHAR(3),
col2 CHAR(3)
);
INSERT INTO tab1 VALUES (1, 3, 'Y');
INSERT INTO tab1 VALUES (2, 43, 'X');
INSERT INTO tab1 VALUES (2, 0, 'Z');
INSERT INTO tab1 VALUES (2, 23, 'F');
CREATE TABLE tab2(
id INT,
col1 CHAR(3),
col2 CHAR(3)
);
INSERT INTO tab2 VALUES (1, 'X', 'Y');
INSERT INTO tab2 VALUES (2, 'X', 'X');
INSERT INTO tab2 VALUES (2, 'Z', 'Z');
INSERT INTO tab2 VALUES (2, 'X', 'X');
CREATE TABLE tab3(
id INT,
col1 CHAR(3)
);
INSERT INTO tab3 VALUES (1, 'ASX');
Sample function:
CREATE FUNCTION get_columns()
RETURNING LVARCHAR(257) AS col;
DEFINE stmt VARCHAR(255);
DEFINE tab_name VARCHAR(128,0);
DEFINE tab_id INTEGER;
DEFINE col_name VARCHAR(128,0);
DEFINE o_tname VARCHAR(128,0);
DEFINE o_cname VARCHAR(128,0);
CREATE TEMP TABLE out_table(
t_name VARCHAR(128,0),
c_name VARCHAR(128,0)
);
CREATE TEMP TABLE tab_v (
col1 VARCHAR(255)
);
INSERT INTO tab_v VALUES ('X');
INSERT INTO tab_v VALUES ('Y');
INSERT INTO tab_v VALUES ('Z');
FOREACH tables FOR
SELECT tabname, tabid
INTO tab_name, tab_id
FROM systables
WHERE tabid > 99 AND tabtype = 'T'
FOREACH column FOR
SELECT colname
INTO col_name
FROM syscolumns
WHERE tabid = tab_id
AND MOD(coltype,256) IN (
0, --CHAR
13, --VARCHAR
15, --NCHAR
16, --NVARCHAR
40, --LVARCHAR
43 --LVARCHAR
)
LET stmt = "INSERT INTO out_table "||
"SELECT '"||tab_name||"', '"||col_name||"' "||
"FROM "||tab_name||" "||
"WHERE EXISTS (SELECT 1 FROM tab_v v WHERE v.col1 = "||col_name||");";
EXECUTE IMMEDIATE stmt;
END FOREACH
END FOREACH
FOREACH out FOR
SELECT UNIQUE t_name, c_name
INTO o_tname, o_cname
FROM out_table
RETURN o_tname||"."||o_cname WITH RESUME;
END FOREACH
DROP TABLE out_table;
DROP TABLE tab_v;
END FUNCTION;
EXECUTE FUNCTION get_columns();

Matching multiple key/value pairs in SQL

I have metadata stored in a key/value table in SQL Server. (I know key/value is bad, but this is free-form metadata supplied by users, so I can't turn the keys into columns.) Users need to be able to give me an arbitrary set of key/value pairs and have me return all DB objects that match all of those criteria.
For example:
Metadata:
Id Key Value
1 a p
1 b q
1 c r
2 a p
2 b p
3 c r
If the user says a=p and b=q, I should return object 1. (Not object 2, even though it also has a=p, because it has b=p.)
The metadata to match is in a table-valued sproc parameter with a simple key/value schema. The closest I have got is:
select * from [Objects] as o
where not exists (
select * from [Metadata] as m
join #data as n on (n.[Key] = m.[Key])
and n.[Value] != m.[Value]
and m.[Id] = o.[Id]
)
My "no rows exist that don't match" is an attempt to implement "all rows match" by forming its contrapositive. This does eliminate objects with mismatching metadata, but it also returns objects with no metadata at all, so no good.
Can anyone point me in the right direction? (Bonus points for performance as well as correctness.)
; WITH Metadata (Id, [Key], Value) AS -- Create sample data
(
SELECT 1, 'a', 'p' UNION ALL
SELECT 1, 'b', 'q' UNION ALL
SELECT 1, 'c', 'r' UNION ALL
SELECT 2, 'a', 'p' UNION ALL
SELECT 2, 'b', 'p' UNION ALL
SELECT 3, 'c', 'r'
),
data ([Key], Value) AS -- sample input
(
SELECT 'a', 'p' UNION ALL
SELECT 'b', 'q'
),
-- here onwards is the actual query
data2 AS
(
-- cnt is to count no of input rows
SELECT [Key], Value, cnt = COUNT(*) OVER()
FROM data
)
SELECT m.Id
FROM Metadata m
INNER JOIN data2 d ON m.[Key] = d.[Key] AND m.Value= d.Value
GROUP BY m.Id
HAVING COUNT(*) = MAX(d.cnt)
The following SQL query produces the result that you require.
SELECT *
FROM #Objects m
WHERE Id IN
(
-- Include objects that match the conditions:
SELECT m.Id
FROM #Metadata m
JOIN #data d ON m.[Key] = d.[Key] AND m.Value = d.Value
-- And discount those where there is other metadata not matching the conditions:
EXCEPT
SELECT m.Id
FROM #Metadata m
JOIN #data d ON m.[Key] = d.[Key] AND m.Value <> d.Value
)
Test schema and data I used:
-- Schema
DECLARE #Objects TABLE (Id int);
DECLARE #Metadata TABLE (Id int, [Key] char(1), Value char(2));
DECLARE #data TABLE ([Key] char(1), Value char(1));
-- Data
INSERT INTO #Metadata VALUES
(1, 'a', 'p'),
(1, 'b', 'q'),
(1, 'c', 'r'),
(2, 'a', 'p'),
(2, 'b', 'p'),
(3, 'c', 'r');
INSERT INTO #Objects VALUES
(1),
(2),
(3),
(4); -- Object with no metadata
INSERT INTO #data VALUES
('a','p'),
('b','q');

Insert into inside cross apply

SELECT DISTINCT CODE
FROM T1
CROSS APPLY
(
INSERT INTO T4(TEXT1, TEXT2, TEXT3)
SELECT T2.TEXT1, T2.TEXT2, T3.TEXT3
FROM T2,
T3
WHERE T2.ID = T3.ID
AND T2.CODE = T1.CODE
) AS T
When using executing this query I get this error:
A nested INSERT, UPDATE, DELETE, or MERGE statement must have an OUTPUT clause.
What am I doing wrong?
EDIT
What I was planning to achieve was simulate a WHILE..LOOP.
Looping through all the CODEs in T1 and for each CODE get the TEXT fields from T2 and T3 (joining them with the ID) and insert them into table T4.
I am trying to separate the inserts by CODE because both tables have a large amount of data and I was trying to improve performance (maybe?!)
Kind of a guess
INSERT INTO FINAL (TEXT1 , TEXT2 , TEXT3)
SELECT DISTINCT T2.TEXT1, T2.TEXT2, T3.TEXT3
FROM T1
JOIN T2
on T2.ID = T1.ID
JOIN T3
on T3.VALUE = T2.VALUE
You can't do what you're attempting to do. The language will not allow you. To set the stage, I created the following 3 tables
SET NOCOUNT ON;
CREATE TABLE dbo.Final
(
ID int IDENTITY(1,1) NOT NULL
, text1 varchar(50) NOT NULL
, text2 varchar(50) NOT NULL
, text3 varchar(50) NOT NULL
);
CREATE TABLE dbo.Chain
(
ID int NOT NULL
);
CREATE TABLE dbo.Chained
(
ID int NOT NULL
, Foo int NOT NULL
);
Just to demo where the OUTPUT clause goes, we will insert 4 rows and see the nice pretty INSERTED virtual table and the associated ID values.
-- Works
INSERT INTO
dbo.Final
(
text1
, text2
, text3
)
OUTPUT
INSERTED.*
SELECT
D.t1
, D.t2
, D.t3
FROM
(
VALUES
('A', 'B', 'C')
, ('D', 'B', 'C')
, ('G', 'B', 'C')
, ('J', 'B', 'C')
) D (t1,t2,t3);
Now, if I perform the following statement, it will work fine. However, if I neglect the INSERT just to visually inspect what I want to put into the table, SQL Server will raise the following error
A nested INSERT, UPDATE, DELETE, or MERGE statement is not allowed in a SELECT statement that is not the immediate source of rows for an INSERT statement.
-- Comment out the insert portion to generate
-- A nested INSERT, UPDATE, DELETE, or MERGE statement is not allowed in a SELECT statement that is not the immediate source of rows for an INSERT statement.
INSERT INTO
dbo.Chain
(
ID
)
SELECT
X.ID
FROM
(
INSERT INTO
dbo.Final
(
text1
, text2
, text3
)
OUTPUT
INSERTED.*
SELECT
D.t1
, D.t2
, D.t3
FROM
(
VALUES
('A', 'B', 'C')
, ('D', 'B', 'C')
, ('G', 'B', 'C')
, ('J', 'B', 'C')
) D (t1,t2,t3)
) x
But, you want to go the extra mile and APPLY, or JOIN the results of that virtual table with something else and that's not going to fly.
A nested INSERT, UPDATE, DELETE, or MERGE statement is not allowed on either side of a JOIN or APPLY operator.
I suppose it's just one level of complexity too much.
-- Now, try the same bit except we use the derived table as a JOIN/APPLY
-- Can't fix what's not supported
-- A nested INSERT, UPDATE, DELETE, or MERGE statement is not allowed on either side of a JOIN or APPLY operator.
INSERT INTO
dbo.Chained
(
ID
, Foo
)
SELECT
X.ID
, D.foo
FROM
(
VALUES
(1)
) D(Foo)
CROSS APPLY
(
INSERT INTO
dbo.Final
(
text1
, text2
, text3
)
OUTPUT
INSERTED.*
SELECT
D.t1
, D.t2
, D.t3
FROM
(
VALUES
('A', 'B', 'C')
, ('D', 'B', 'C')
, ('G', 'B', 'C')
, ('J', 'B', 'C')
) D (t1,t2,t3)
) x;
If you really need something like that, then you'll have to break it out into separate statements.

How to get a fixed amount of results per row?

How can I build a query with this format with a sqlite3 database?
CREATE TABLE sample (integer foo);
INSERT INTO sample VALUES (1);
...
INSERT INTO sample VALUES (10);
Format of the result
1,2,3
4,5,6
7,8,9
10
You'll have to add some criteria to group them:
CREATE TABLE sample (integer foo, char(1) bar);
INSERT INTO sample VALUES
(1, 'a'), (2, 'a'), (3, 'a'), (4, 'b'), (5, 'b'), (6, 'b'), (7, 'c') ...;
SELECT GROUP_CONCAT(foo ORDER BY foo, ',')
FROM sample
GROUP BY bar
Edit:
Try this:
select group_concat(foo) from (
select s1.foo, (count(*) - 1) / 3 grp from sample s1
join sample s2 on s1.rowid >= s2.rowid
group by s1.rowid
) final
group by grp
This might not be the best solution (and has some edge cases---if you have a 3 value that is not null), and can probably be put into one query, but I needed the RowId to join on. It should do the trick, though:
CREATE TEMP TABLE split1 (foo1 int);
CREATE TEMP TABLE split2 (foo2 int);
CREATE TEMP TABLE split3 (foo3 int);
INSERT INTO split1
SELECT foo FROM sample WHERE foo % 3 = 1 ORDER BY foo
INSERT INTO split2
SELECT foo FROM sample WHERE foo % 3 = 2 ORDER BY foo
INSERT INTO split3
SELECT foo FROM sample WHERE foo % 3 = 0 ORDER BY foo
SELECT
CASE
WHEN foo2 IS NULL THEN foo1
WHEN foo3 IS NULL THEN foo1||','||foo2
ELSE foo1||','||foo2||','||foo3
END
FROM split1
LEFT JOIN split2
ON split1.RowId = split2.RowId
LEFT JOIN split3
ON split2.RowId = split3.RowId