I am trying to convert a proprietary Oracle CONNECT BY query into a standard SQL query that will run on H2, and generate the same data in the same order.
This is the Oracle query, which works:
SELECT id, name, parent
FROM myschema.mytable
START WITH id = 1
CONNECT BY PRIOR id = parent
This is what I've come up - however, it returns no rows in the ResultSet.
WITH RECURSIVE T(id, name, parent, path) AS (
SELECT id, name, '' AS parent, id AS path
FROM myschema.mytable WHERE id = 1
UNION ALL
SELECT ou.id, ou.name, ou.parent,
(T.path + '.' + CAST (ou.id AS VARCHAR)) AS path
FROM T INNER JOIN myschema.mytable AS ou ON T.id = ou.parent
) SELECT id, name, parent FROM T ORDER BY path
The initial row, and the related rows, both exist in the table.
I am not using H2's Oracle compatibility mode (which doesn't support CONNECT BY, by the way).
The following works for me, for both H2 as well as PostgreSQL (this you can test online using the SQL Fiddle). I had to make a few changes and assumptions (see below):
create table mytable(id int, name varchar(255), parent int);
insert into mytable values(1, 'root', null), (2, 'first', 1),
(3, 'second', 1), (4, '2b', 3);
WITH RECURSIVE T(id, name, parent, path) AS (
SELECT id, name, 0 AS parent,
cast(id as varchar) AS path
FROM mytable WHERE id = 1
UNION ALL
SELECT ou.id, ou.name, ou.parent,
(T.path || '.' || CAST (ou.id AS VARCHAR)) AS path
FROM T INNER JOIN mytable AS ou ON T.id = ou.parent
) SELECT id, name, parent, path FROM T ORDER BY path
Changes:
I assumed id and parent are integers. Because of that, I had to use cast(id as varchar) in the first select.
I replace + with || when concatenating strings.
I used 0 AS parent.
This seems to have been a problem with either the Anorm database access library or the JDBC driver not substituting a query parameter correctly (the query substitution was not shown in the question, because I assumed it wasn't relevant).
Related
I have a table in by database that has a name field with a non-null and unique constraint.
On the frontend, a user can clone certain entities. In this scenario, the name field gets suffixed with a version number.
For instance, if record A exists with the name TEST_NAME, cloning this record would result in record B being created with the name TEST_NAME [2]. Cloning record A again would result in a record C with the name TEST_NAME [3].
To determine the version number, I run a count(*) against the table, returning the number of records that match the root name, in this case: 'TEST_NAME'.
Here is the query:
SELECT COUNT(*)
FROM my_table
WHERE name LIKE 'TEST_NAME%'
The issue here, is that if a user changes the name of record C to TEST_NAME [3]abc, then the above query would still pick this up and create record D with the name TEST_NAME [4], instead of TEST_NAME [3]abc [2].
How can I avoid this? I'd like to only match name values that follow the format ^TEST_NAME [x]$, where x is any integer.
PostgreSQL has support for regexes.
For instance, I guess what you need is
SELECT COUNT(*)
FROM my_table
WHERE name ~ '^TEST_NAME \[[0-9]+\]$'
For computing the next version, I propose the following :
SELECT
version,
COALESCE(
matches[1] || ' [' || matches[2]::int + 1 || ']',
matches[3] || ' [2]'
) AS nextVersion
FROM versions
CROSS JOIN LATERAL (
SELECT regexp_matches(version, '^(.*) \[([0-9]+)\]$|^(.*)$') matches
) t
Here is what's going on :
For each version, we match the regexp ^(.)[([0-9]+)]$|^(.)$. Groups 1 and 2 will be populated if the version ends with a version number. Group 3 always contains the whole name. We put this result in the lateral table t(matches).
If group 1 and 2 have values, then "matches[1] || ' [' || matches[2]::int + 1 || ']'" is the next version, otherwise pick matches[3] and add [2] to it.
As a bonus, the following query will give the last version for every rootname, and the next version available.
SELECT rootname, MAX(t2.version) AS lastVersion, MAX(t2.version) + 1 AS nextVersion
FROM versions
CROSS JOIN LATERAL (
SELECT regexp_matches(version, '^(.*) \[([0-9]+)\]$|^(.*)$') matches
) t1
CROSS JOIN LATERAL (
SELECT
COALESCE(matches[1], matches[3]) AS rootname,
COALESCE(matches[2]::int, 1) AS version
) t2
GROUP BY rootname;
If you just have a rootname (say TEST_NAME), and assuming you only have one column version in your table which would be called versions, you can clone the record using :
INSERT INTO versions
SELECT rootname || ' [' || MAX(t2.version) + 1 || ']'
FROM versions
CROSS JOIN LATERAL (
SELECT regexp_matches(version, '^(.*) \[([0-9]+)\]$|^(.*)$') matches
) t1
CROSS JOIN LATERAL (
SELECT
COALESCE(matches[1], matches[3]) AS rootname,
COALESCE(matches[2]::int, 1) AS version
) t2
WHERE rootname = 'TEST_NAME';
I have a table like this:
I want to concatenate the Product name in the given Product_order by ID.
Therefore, I should get something like: CC-TC-CA for ID 1.
you can use string_agg()- it'll work sql server 2016+
select id, string_Agg(product,',') as pname
from tablename
group by id
OR you can use stuff()
SELECT id,
STUFF((SELECT ',' + product
FROM tablename AS T1
WHERE T1.id = T2.id
FOR XML PATH('')), 1, 1, '')
FROM tablename AS T2
GROUP BY id
If you can use a stored procedure instead of a single query the += operator can do what you're looking for.
DECLARE #Product_order VARCHAR(100) = '';
SELECT #Product_order += Product + '-' FROM [table] WHERE id = 1 ORDER BY Product_Order;
SELECT SUBSTRING(#Product_order, 0, LEN(#Product_order));
Update: I've learned that returning multiple rows and using in an assignment in the select clause is unsupported behavior in SQL Server.
I am trying to turn my sql results into a temp table but it keeps on returning the following error:
Expecting As or Id or quoted-ID.
I know the sql result is correct but when I add SELECT * INTO #newtable FROM, the sql query doesn't work. What is wrong?
SELECT *
INTO #newtable
FROM
(
SELECT
Url,Id
FROM Blob
WHERE
Id IN
(
SELECT
BlobId
FROM
XrefBlobProjectMeeting
)
AND Extension NOT IN ('xlsx','xls','avi','jpg','mp4','wmv','png')
AND (RefContentTypeId IN (11,13,14,35))
)
Not sure why you have the nested SELECTs. This is what you're more likely after, as you then don't have to alias your subquery:
SELECT Url,
Id
INTO #newtable
FROM Blob
WHERE Id IN (SELECT BlobId FROM XrefBlobProjectMeeting)
AND Extension NOT IN ('xlsx', 'xls', 'avi', 'jpg', 'mp4', 'wmv', 'png')
AND (RefContentTypeId IN (11, 13, 14, 35));
You'd be ever better off, however, changing the IN to an EXISTS as well though:
SELECT [Url],
Id
INTO #newtable
FROM Blob B
WHERE EXISTS (SELECT 1
FROM XrefBlobProjectMeeting E
WHERE E.BlobID = B.ID)
AND Extension NOT IN ('xlsx', 'xls', 'avi', 'jpg', 'mp4', 'wmv', 'png')
AND RefContentTypeId IN (11, 13, 14, 35);
I have tried your problem in your way only and it is working for me. Solution is like given below;
SELECT Url,Id
INTO #newtable
FROM Blob
WHERE
Id IN
(
SELECT
BlobId
FROM
XrefBlobProjectMeeting
)
AND Extension NOT IN ('xlsx','xls','avi','jpg','mp4','wmv','png')
AND (RefContentTypeId IN (11,13,14,35))
I have a table containing the fields group_id and group_type and I want to query the table for all the records having any tuple (group id, group type) from a list of tuples. For example, I want to be able to do something like:
SELECT *
FROM mytable
WHERE (group_id, group_type) IN (("1234-567", 2), ("4321-765", 3), ("1111-222", 5))
A very similar question is already asked at: using tuples in sql in clause , but the solution proposed there presumes the tuple list is to be fetched from another table. This doesn't work in my case is the tuple values are hard coded.
One solution is to use string concatenation:
SELECT *
FROM mytable
WHERE group_id + STR(group_type, 1) IN ("1234-5672", "4321-7653", "1111-2225")
But the problem is that the table is quite big and doing a string concatenation and conversion for each record would be very expensive.
Any suggestion?
Given a very minor tweak (replace double quotes with single and add the VALUES keyword), your proposed syntax is valid Standard SQL-92 syntax i.e.
SELECT *
FROM mytable
WHERE (group_id, group_type) IN (
VALUES ('1234-567', 2),
('4321-765', 3),
('1111-222', 5)
);
Sadly, MSFT have not added it to SQL Server and consider it an 'unplanned' feature.
FWIW PostgreSQL and Sqlite are examples of SQL products that support this syntax.
In SQL Server 2008 you can do like this:
select *
from mytable as T
where exists (select *
from (values ('1234-567', 2),
('4321-765', 3),
('1111-222', 5)) as V(group_id, group_type)
where T.group_id = V.group_id and
T.group_type = V.group_type
)
EDIT: this is a dated answer, although it was the accepted answer in 2011, other answers with more upvotes reflect more recent approaches.
Why not construct the OR statements?
SELECT *
FROM mytable
WHERE (group_id = '1234-567' and group_type = 2)
OR (group_id = '4321-765' and group_type = 3)
OR (group_id = '1111-222' and group_type = 5)
Granted, it doesn't look as nice and neat as your concept example but it will do the job (and if you IN with tuples did exist, it would implement it exactly the same way under the covers most likely.
You can use a common table expression to pretend that these tuples are in another table:
;WITH Tuples as (
select '1234-567' as group_id, 2 as group_type union all
select '4321-765', 3 union all
select '1111-222', 5
)
SELECT * /* TODO - Pick appropriate columns */
from mytable m where exists (
select * from Tuples t
where m.group_id = t.group_id and m.group_type = t.group_type)
Using that solution, this should work:
SELECT *
FROM mytable m
WHERE EXISTS (
SELECT * FROM (
SELECT "1234-567" group_id, 2 group_type UNION ALL
SELECT "4321-765", 3 UNION ALL
SELECT "1111-222", 5) [t]
WHERE m.group_id = t.group_id AND m.group_type = t.group_type)
BTW, you should probably use a CTE to create that inner table.
I haven't seen this yet, but something like this should work
SELECT * FROM AgeGroup ag JOIN
(VALUES
('18-24', 18, 24),
('25-34 ', 25, 39),
('35-44 ', 35, 49),
('45-54 ', 45, 59),
('55-64 ', 55, 69),
('65+ ', 65, 299)
) AS x (agegroup, minage, maxage)
ON ag.age_group = x.agegroup
AND ag.min_age=x.minage
AND ag.max_age=x.maxage
Here is another tuple solution using a join:
SELECT
*
FROM mytable m
JOIN
(
SELECT "1234-567" group_id, 2 group_type
UNION ALL SELECT "4321-765", 3
UNION ALL SELECT "1111-222", 5
) [t]
ON m.group_id = t.group_id
AND m.group_type = t.group_type
I had a similar problem but my tuple collection was dynamic - it was sent over to the SQL Server in a query parameter. I came up with the following solution:
Pass a tuple as an XML:
DECLARE #tuplesXml xml = '<tuples><tuple group-id="1234-567" group-type="2"/><tuple group-id="4321-765" group-type="3"/></tuples>';
Inner join the table that you want to filter with the XML nodes:
SELECT t.* FROM mytable t
INNER JOIN #tuplesXml.nodes('/tuples/tuple') AS tuple(col)
ON tuple.col.value('./#group-id', 'varchar(255)') = t.group_id
AND tuple.col.value('./#group-type', 'integer') = t.group_type
It seems to work fine in my situation which is a bit more complex than the one described in the question.
Keep in mind that it is necessary to use t.* instead of * and the table returned from nodes method needs to be aliased (it's tuple(col) in this case).
select * from table_name where 1=1 and (column_a, column_b) in ((28,1),(25,1))
I have a query like this:
SELECT Name,
REPLACE(RTRIM((
SELECT CAST(Score AS VARCHAR(MAX)) + ' '
FROM
(SELECT Name, Score
FROM table
WHERE
---CONDITIONS---
) AS InnerTable
WHERE (InnerTable.Name = OuterTable.Name) FOR XML PATH (''))),' ',', ') AS Scores
FROM table AS OuterTable
WHERE
---CONDITIONS---
GROUP BY Name;
As it can be seen, I am using the same set of conditions to derive the InnerTable and OuterTable. Is there a way to shorten this query? I am asking this because, sometime back, I saw a keyword USING in MySQL that simplified my life using which you can specify a query once and then use its alias for the rest of the query.
You could look at creating a Common Table Expression (CTE). That is your best bet for aliasing a select. Unfortunately I'm not sure how much shoerter it will make your query though it does prevent you from defining the where conditions twice. see below:
with temp as
(
SELECT Name, Score
FROM table
WHERE whatever = 'whatever'
)
SELECT Name,
REPLACE(RTRIM((
SELECT CAST(Score AS VARCHAR(MAX)) + ' '
FROM
(SELECT Name, Score
FROM temp ) AS InnerTable
WHERE (InnerTable.Name = OuterTable.Name) FOR XML PATH (''))),' ',', ') AS Scores
FROM temp AS OuterTable
GROUP BY Name;