Apply COALESCE between multiple fields in multiple unrelated tables - sql

We have a table that contains configuration data for our application, and a separate table that contains global configuration data for our platform. In this instance, each table has a set of two fields to coalesce, as one of the fields is a specific value and one field is more globally considered. But if neither field is set in the application table I need the global configuration data checked in a similar way. For example our application data references "questions" and a query to get the correct value will look like:
SELECT COALESCE(QuestionStyleDoc,StyleDoc) AS Style
FROM TABLE1
WHERE ID = 1000
So that for instance 1000 I retrieve the value for questions, but if not set specifically for questions I get the main value set for instance 1000. But if neither of these values are configured I have a global configuration I can check:
SELECT COALESCE(Table1StyleDoc,WebStyleDoc) AS Style
FROM TABLE2
WHERE [KEY] = 1
That contains a value to be considered for all unset Table1 applications, or if not set a truly global value. Basically, I need to return the first non-null value like
COALESCE(TABLE1.QuestionStyleDoc,TABLE1.StyleDoc,TABLE2.Table1StyleDoc,TABLE2.WebStyleDoc) AS Style
But so far, have not had any luck between attempts to join or union the data. The goal is to get this to happen in a single query to remove round trips to the database for the application.

I think that you might be after a FULL JOIN:
SELECT COALESCE(t1.QuestionStyleDoc, t1.StyleDoc, t2.Table1StyleDoc, t2.WebStyleDoc) style
FROM (
SELECT QuestionStyleDoc, StyleDoc
FROM TABLE1
WHERE ID = 1000
) t1
FULL JOIN (
SELECT Table1StyleDoc, WebStyleDoc
FROM TABLE2
WHERE [KEY] = 1
) t2 ON 1 = 1

I would suggest you to create a scalar user defined function to get the global style. I am passing key as parameter. As you are suggesting, Key will always be 1. But, I am adding this as parameter, if you need to call the UDF for different key.
CREATE FUNCTION dbo.GetGlobalStyle(#Key int)
RETURNS VARCHAR(50)
AS
-- Returns global style
BEGIN
DECLARE #ret VARCHAR(50);
SET #ret = (SELECT COALESCE(Table1StyleDoc,WebStyleDoc) AS Style
FROM TABLE2
WHERE [KEY] = #key)
RETURN #ret;
END;
Now, refer this UDF in your SELECT statement for every application, so don't need to repeat the code.
SELECT COALESCE(QuestionStyleDoc,StyleDoc, dbo.GetGlobalStyle(1)) AS Style
FROM TABLE1
WHERE ID = 1000

Related

How do I use SQL Server to select into another table?

I am consolidating a web service. I am replacing multiple calls to the service with one call that contains the data.
I have created a table:
CREATE TABLE InvResults
(
Invoices nvarchar(max),
InvoiceDetails nvarchar(max),
Products nvarchar(max)
);
I used (max) because I don't know how complex the json will get at this time.
I need to do some sort of selects like this (this is pseudocode, not actual SQL):
SELECT
(SELECT *
INTO InvResults for Column Invoices
FROM MyInvoiceTable
WHERE SomeColumns = 'someStuffvariable'
FOR JSON PATH, ROOT('invoices')) AS invoices;
SELECT
(SELECT *
INTO InvResults for Column InvoiceDetails
FROM MyInvoiceDetailsTable
WHERE SomeColumns = 'someStuffvariable'
FOR JSON PATH, ROOT('invoicedetails')) AS invoicedetails;
I don't know how to format this and my google skills are failing me at this point. I understand that I probably want to use an UPDATE statement, but I'm not sure how to do this in combination with the rest of my requirements. I'm exploring How do I UPDATE from a SELECT in SQL Server? but I am still at a halt.
The end result should be a table "InvResults" that has 3 columns containing one row with results from Select statements as JSON. The column names should be defined the same as the json root objects.
INSERT INTO InvResults(Invoices,InvoidesDetails)
SELECT
(SELECT *
INTO InvResults for Column Invoices
FROM MyInvoiceTable
WHERE SomeColumns = 'someStuffvariable'
FOR JSON PATH, ROOT('invoices'))
,
(SELECT *
INTO InvResults for Column InvoiceDetails
FROM MyInvoiceDetailsTable
WHERE SomeColumns = 'someStuffvariable'
FOR JSON PATH, ROOT('invoicedetails'))
;
Because the SELECT.. FOR JSON is only returning 1 row above works.
The third field is easily to added, but left to do for yourself 😉

Use value of one column as identifier in another table in SNOWFLAKE

I have two table one of which contains the rule for another
create table t1(id int, query string)
create table t2(id int, place string)
insert into t1 values (1,'id < 10')
insert into t1 values (2,'id == 10')
And the values in t2 are
insert into t2 values (11,'Nevada')
insert into t2 values (20,'Texas')
insert into t2 values (10,'Arizona')
insert into t2 values (2,'Abegal')
I need to select from second table as per the value of first table column value.
like
select * from t2 where {query}
or
with x(query)
as
(select c2 from test)
select * from test where query;
but neither are helping.
There are a couple of problems with storing criteria in a table like this:
First, as has already been noted, you'll likely have to resort to dynamic SQL, which can get messy, and limits how you can use it.
It's going to be problematic (to say the least) to validate and parse your criteria. What if someone writes a rule of [id] *= 10, or [this_field_doesn't_exist] = blah?
If you're just storing potential values for your [id] column, one solution would be to have your t1 (storing your queries) include a min value and max value, like this:
CREATE TABLE t1
(
[id] INT IDENTITY(1,1) PRIMARY KEY,
min_value INT NULL,
max_value INT NULL
)
Note that both the min and max values can be null. Your provided criteria would then be expressed as this:
INSERT INTO t1
([id], min_value, max_value)
VALUES
(1, NULL, 10),
(2, 10, 10)
Note that I've explicitly referenced what attibutes we're inserting into, as you should also be doing (to prevent issues with attributes being added/modified down the line).
A null value on min_value means no lower limit; a null max_value means no upper limit.
To then get results from t2 that meet all your t1 criteria, simply do an INNER JOIN:
SELECT t2.*
FROM t2
INNER JOIN t1 ON
(t2.id <= t1.max_value OR t1.max_value IS NULL)
AND
(t2.id >= t1.min_value OR t1.min_value IS NULL)
Note that, as I said, this will only return results that match all your criteria. If you need to more complex logic (for example, show records that meet Rules 1, 2 and 3, or meet Rule 4), you'll likely have to resort to dynamic SQL (or at the very least some ugly JOINs).
As stated in a comment, however, you want to have more complex rules, which might mean you have to end up using dynamic SQL. However, you still have the problem of validating and parsing your rule. How do you handle cases where a user enters an invalid rule?
A better solution might be to store your rules in a format that can easily be parsed and validated. For example, come up with an XML schema that defines a valid rule/criterion. Then, your Rules table would have a rule XML attribute, tied to that schema, so users could only enter valid rules. You could then either shred that XML document, or create the SQL client-side to come up with your query.
I got the answer myself. And I am putting it below.
I have used python CLI to do the job. (As snowflake does not support dynamic query)
I believe one can use the same for other DB (tedious but doable)
setting up configuration to connect
CONFIG_PATH = "/root/config/snowflake.json"
with open(CONFIG_PATH) as f:
config = json.load(f)
#snowflake
snf_user = config['snowflake']['user']
snf_pwd = config['snowflake']['pwd']
snf_account = config['snowflake']['account']
snf_region = config['snowflake']['region']
snf_role = config['snowflake']['role']
ctx = snowflake.connector.connect(
user=snf_user,
password=snf_pwd,
account=snf_account,
region=snf_region,
role=snf_role
)
--comment
Used multiple cursor as in loop we don't want recursive connection
cs = ctx.cursor()
cs1 = ctx.cursor()
query = "select c2 from test"
cs.execute(query)
for (x) in cs:
y = "select * from test1 where {0}".format(', '.join(x).replace("'",""))
cs1.execute(y)
for (y1) in cs1:
print('{0}'.format(y1))
And boom done

T-SQL Skip cte Table and Union when Variable Is Null

I've got a query that captures all shipments and costs from our factory. Sample data and desired output on Google Drive here:
https://drive.google.com/open?id=0B4xdnV0LFZI1VndEaGgxNDVpU2M
The issue is we've got 2 different ways of selling things. One is 'Regluar' where we make it and the other one is a 'buy/sell' where we buy and sell it.
To capture the costs I've had to write two queries, one for each scenario. The end users of this query can enter in a date range and the query works well then, but I'm stuck when it comes to the variable #Job_No.
All work that goes through our factory (cteRegularJobs) has a Job Number associated with it and I've declared a variable so users can use it to search. The cteBuyandSell has a value of 'NULL' for Job_No declared in the SELECT statement so I can do a UNION of these two tables at the end. However, no buy/sell jobs have Job Numbers assigned to them, they are always NULL.
Initially #Job_No is declared as '' and when it's left '' I want the results from both cte tables returned. If there is an entry by the user, i.e. '001' then I want results for cteRegularJobs.
If it makes it easier I am open to declaring the cteBuyandSell.Job_No something else besides NULL, like "Buy and Sell".
The real query is complicated so here's a simplified example of the structure:
DELCARE #Job_No AS varchar(10) = '';
SET #Job_No = {User Input or leave as ''};
WITH
cteBuyandSell AS ( NULL AS 'Job_No',
...),
cteRegularJobs AS (tblJobs.Job_No AS 'Job_No',
...
WHERE tblJobs.Job_No LIKE #Job_No)
SELECT *
FROM
(cteBuyandSell
UNION
cteRegularJobs)
You can logically break this up with an IF statement to check the value of your variable. I'd suggest NULL over white space though. Here's an example procedure... with the limited code you provided.
CREATE PROCEDURE getData(#Job_No varchar(10) = NULL)
AS
IF #Job_No IS NULL
BEGIN
WITH
cteBuyandSell AS ( NULL AS 'Job_No',
...),
cteRegularJobs AS (tblJobs.Job_No AS 'Job_No',
...
WHERE tblJobs.Job_No LIKE #Job_No)
SELECT *
FROM
(cteBuyandSell
UNION
cteRegularJobs)
END
ELSE
BEGIN
WITH
cteRegularJobs AS (tblJobs.Job_No AS 'Job_No',
...
WHERE tblJobs.Job_No LIKE #Job_No)
SELECT * FROM cteRetularJobs
END

How do you query an int column for any value?

How can you query a column for any value in that column? (ie. How do I build a dynamic where clause that can either filter the value, or not.)
I want to be able to query for either a specific value, or not. For instance, I might want the value to be 1, but I might want it to be any number.
Is there a way to use a wild card (like "*"), to match any value, so that it can be dynamically inserted where I want no filter?
For instance:
select int_col from table where int_col = 1 // Query for a specific value
select int_col from table where int_col = * // Query for any value
The reason why I do not want to use 2 separate SQL statements is because I am using this as a SQL Data Source, which can only have 1 select statement.
Sometimes I would query for actual value (like 1, 2...) so I can't not have a condition either.
I take it you want some dynamic behavior on your WHERE clause, without having to dynamically build your WHERE clause.
With a single parameter, you can use ISNULL (or COALESCE) like this:
SELECT * FROM Table WHERE ID = ISNULL(#id, ID)
which allows a NULL parameter to match all. Some prefer the longer but more explicit:
SELECT * FROM Table WHERE (#id IS NULL) OR (ID = #id)
A simple answer would be use: IS NOT NULL. But if you are asking for say 123* for numbers like 123456 or 1234 or 1237 then the you could convert it to a varchar and then test against using standard wild cards.
In your where clause: cast(myIntColumn as varchar(15)) like '123%'.
Assuming the value you're filtering on is a parameter in a stored procedure, or contained in a variable called #Value, you can do it like this:
select * from table where #Value is null or intCol = #Value
If #Value is null then the or part of the clause is ignored, so the query won't filter on intCol.
The equivalent of wildcards for numbers are the comparators.
So, if you wanted to find all positive integers:
select int_col from table where int_col > 0
any numbers between a hundred and a thousand:
select int_col from table where int_col BETWEEN 100 AND 1000
and so on.
I don't quite understand what you're asking. I think you should use two different queries for the different situations you have.
When you're not looking for a specific value:
SELECT * FROM table
When you are looking for a specific value:
SELECT * FROM table WHERE intcol = 1
You can use the parameter as a wildcard by assigning special meaning to NULL:
DECLARE #q INT = 1
SELECT * FROM table WHERE IntegerColumn = #q OR #q IS NULL
This way, when you pass in NULL; you get all rows.
If NULL is a valid value to query for, then you need to use two parameters.
If you really want the value of your column for all rows on the table you can simply use
select int_col
from table
If you want to know all the distinct values, but don't care how many times they're repeated you can use
select distinct int_col
from table
And if you want to know all the distinct values and how many times they each appear, use
select int_col, count(*)
from table
group by int_col
To have the values sorted properly you can add
order by int_col
to all the queries above.
Share and enjoy.

SQL pivoted table is read-only and cells can't be edited?

If I create a VIEW using this pivot table query, it isn't editable. The cells are read-only and give me the SQL2005 error: "No row was updated. The data in row 2 was not committed. Update or insert of view or function 'VIEWNAME' failed because it contains a derived or constant field."
Any ideas on how this could be solved OR is a pivot like this just never editable?
SELECT n_id,
MAX(CASE field WHEN 'fId' THEN c_metadata_value ELSE ' ' END) AS fId,
MAX(CASE field WHEN 'sID' THEN c_metadata_value ELSE ' ' END) AS sID,
MAX(CASE field WHEN 'NUMBER' THEN c_metadata_value ELSE ' ' END) AS NUMBER
FROM metadata
GROUP BY n_id
Assuming you have a unique constraint on n_id, field which means that at most one row can match you can (in theory at least) use an INSTEAD OF trigger.
This would be easier with MERGE (but that is not available until SQL Server 2008) as you need to cover UPDATES of existing data, INSERTS (Where a NULL value is set to a NON NULL one) and DELETES where a NON NULL value is set to NULL.
One thing you would need to consider here is how to cope with UPDATES that set all of the columns in a row to NULL I did this during testing the code below and was quite confused for a minute or two until I realised that this had deleted all the rows in the base table for an n_id (which meant the operation was not reversible via another UPDATE statement). This issue could be avoided by having the VIEW definition OUTER JOIN onto what ever table n_id is the PK of.
An example of the type of thing is below. You would also need to consider potential race conditions in the INSERT/DELETE code indicated and whether you need some additional locking hints in there.
CREATE TRIGGER trig
ON pivoted
INSTEAD OF UPDATE
AS
BEGIN
SET nocount ON;
DECLARE #unpivoted TABLE (
n_id INT,
field VARCHAR(10),
c_metadata_value VARCHAR(10))
INSERT INTO #unpivoted
SELECT *
FROM inserted UNPIVOT (data FOR col IN (fid, sid, NUMBER) ) AS unpvt
WHERE data IS NOT NULL
UPDATE m
SET m.c_metadata_value = u.c_metadata_value
FROM metadata m
JOIN #unpivoted u
ON u.n_id = m.n_id
AND u.c_metadata_value = m.field;
/*You need to consider race conditions below*/
DELETE FROM metadata
WHERE NOT EXISTS(SELECT *
FROM #unpivoted u
WHERE metadata.n_id = u.n_id
AND u.field = metadata.field)
INSERT INTO metadata
SELECT u.n_id,
u.field,
u.c_metadata_value
FROM #unpivoted u
WHERE NOT EXISTS (SELECT *
FROM metadata m
WHERE m.n_id = u.n_id
AND u.field = m.field)
END
You'll have to create trigger on view, because direct update is not possible:
CREATE TRIGGER TrMyViewUpdate on MyView
INSTEAD OF UPDATE
AS
BEGIN
SET NOCOUNT ON;
UPDATE MyTable
SET ...
FROM INSERTED...
END